diff --git a/README.md b/README.md
index 79fc1321d9b75382761c6c1e45b79b5619bff5c8..b39e7bb558fc21eca452bfeeefe994e356032c51 100644
--- a/README.md
+++ b/README.md
@@ -37,10 +37,18 @@
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 f408265a98906e16aba0800b49d797b10fc4bfd2..159ce0ed3bd3d39fb6d79a9f3ebe45d99781b147 100644
--- a/src/main/java/me/zhyd/oauth/config/AuthSource.java
+++ b/src/main/java/me/zhyd/oauth/config/AuthSource.java
@@ -469,6 +469,46 @@ public enum AuthSource {
public String userInfo() {
return "https://api.renren.com/v2/user/get";
}
+ },
+
+ /**
+ * Pinterest
+ */
+ PINTEREST {
+ @Override
+ public String authorize() {
+ return "https://api.pinterest.com/oauth";
+ }
+
+ @Override
+ public String accessToken() {
+ return "https://api.pinterest.com/v1/oauth/token";
+ }
+
+ @Override
+ public String userInfo() {
+ return "https://api.pinterest.com/v1/me";
+ }
+ },
+
+ /**
+ * Stack Overflow
+ */
+ STACK_OVERFLOW {
+ @Override
+ public String authorize() {
+ return "https://stackoverflow.com/oauth";
+ }
+
+ @Override
+ public String accessToken() {
+ return "https://stackoverflow.com/oauth/access_token/json";
+ }
+
+ @Override
+ public String userInfo() {
+ return "https://api.stackexchange.com/2.2/me";
+ }
};
/**
diff --git a/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java b/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java
index c044a0d141dbcc62e5682feaf586dbcdd563741e..aa004e54c54843248bb617c9e5aded235c45730c 100644
--- a/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java
+++ b/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java
@@ -139,7 +139,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 993e7f70ee16e953463fb5e5e2faf339f7482f55..b66c2e27fa16a8df7b6ae3c27f737ebb4f1a433d 100644
--- a/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java
+++ b/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java
@@ -13,6 +13,8 @@ import me.zhyd.oauth.utils.UrlBuilder;
import java.util.HashMap;
import java.util.Map;
+import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap;
+
/**
* 微软登录
*
@@ -37,12 +39,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/AuthPinterestRequest.java b/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6bdcb73482aabeff4f7b8115fd834dbe200be5d
--- /dev/null
+++ b/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java
@@ -0,0 +1,76 @@
+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.AuthPinterestUrlBuilder;
+import me.zhyd.oauth.url.entity.AuthUserInfoEntity;
+
+import java.util.Objects;
+
+import static me.zhyd.oauth.config.AuthSource.PINTEREST;
+
+/**
+ * Pinterest登录
+ *
+ * @author hongwei.peng (pengisgood(at)gmail(dot)com)
+ * @version 1.9.0
+ * @since 1.9.0
+ */
+public class AuthPinterestRequest extends AuthDefaultRequest {
+
+ public AuthPinterestRequest(AuthConfig config) {
+ super(config, PINTEREST, new AuthPinterestUrlBuilder());
+ }
+
+ @Override
+ protected AuthToken getAccessToken(AuthCallback authCallback) {
+ String accessTokenUrl = this.urlBuilder.getAccessTokenUrl(authCallback.getCode());
+ HttpResponse response = HttpRequest.post(accessTokenUrl).execute();
+ JSONObject accessTokenObject = JSONObject.parseObject(response.body());
+ if (!response.isOk()) {
+ throw new AuthException("Unable to get token from Pinterest using code [" + authCallback.getCode() + "]: " + accessTokenObject);
+ }
+
+ return AuthToken.builder()
+ .accessToken(accessTokenObject.getString("access_token"))
+ .tokenType(accessTokenObject.getString("token_type"))
+ .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()).getJSONObject("data");
+
+ return AuthUser.builder()
+ .uuid(userObj.getString("id"))
+ .avatar(getAvatarUrl(userObj))
+ .username(userObj.getString("username"))
+ .nickname(userObj.getString("first_name") + " " + userObj.getString("last_name"))
+ .gender(AuthUserGender.UNKNOWN)
+ .remark(userObj.getString("bio"))
+ .token(authToken)
+ .source(PINTEREST)
+ .build();
+ }
+
+ private String getAvatarUrl(JSONObject userObj) {
+ // image is a map data structure
+ JSONObject jsonObject = userObj.getJSONObject("image");
+ if (Objects.isNull(jsonObject)) {
+ return null;
+ }
+ return jsonObject.getJSONObject("60x60").getString("url");
+ }
+
+}
diff --git a/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java b/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java
index 47ce130bdd2c2c322d62bd83555f159a57bc5d7e..616b7867c550a60b0f873996b40c4fe6907c5ff3 100644
--- a/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java
+++ b/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java
@@ -6,6 +6,7 @@ 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.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.model.AuthUserGender;
@@ -14,6 +15,7 @@ import me.zhyd.oauth.utils.UrlBuilder;
import java.util.Objects;
import static me.zhyd.oauth.config.AuthSource.RENREN;
+import static me.zhyd.oauth.model.AuthResponseStatus.SUCCESS;
/**
* 人人登录
@@ -30,22 +32,14 @@ public class AuthRenrenRequest extends AuthDefaultRequest {
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
- HttpResponse response = doPostAuthorizationCode(authCallback.getCode());
- JSONObject accessTokenObject = JSONObject.parseObject(response.body());
- if (!response.isOk()) {
- throw new AuthException("Unable to get token from renren using code [" + authCallback.getCode() + "]: " + accessTokenObject);
- }
-
- return AuthToken.builder()
- .accessToken(accessTokenObject.getString("access_token"))
- .refreshToken(accessTokenObject.getString("refresh_token"))
- .openId(accessTokenObject.getJSONObject("user").getString("id"))
- .build();
+ return getToken(this.urlBuilder.getAccessTokenUrl(authCallback.getCode()));
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
+
HttpResponse response = doGetUserInfo(authToken);
+
JSONObject userObj = JSONObject.parseObject(response.body()).getJSONObject("response");
return AuthUser.builder()
@@ -59,6 +53,30 @@ public class AuthRenrenRequest extends AuthDefaultRequest {
.build();
}
+ @Override
+ public AuthResponse refresh(AuthToken authToken) {
+ return AuthResponse.builder()
+ .code(SUCCESS.getCode())
+ .data(getToken(this.urlBuilder.getRefreshUrl(authToken.getRefreshToken())))
+ .build();
+ }
+
+ private AuthToken getToken(String url) {
+ HttpResponse response = HttpRequest.post(url).execute();
+ JSONObject jsonObject = JSONObject.parseObject(response.body());
+ if (!response.isOk()) {
+ throw new AuthException("Failed to get token from Renren: " + jsonObject);
+ }
+
+ return AuthToken.builder()
+ .tokenType(jsonObject.getString("token_type"))
+ .expireIn(jsonObject.getIntValue("expires_in"))
+ .accessToken(jsonObject.getString("access_token"))
+ .refreshToken(jsonObject.getString("refresh_token"))
+ .openId(jsonObject.getJSONObject("user").getString("id"))
+ .build();
+ }
+
private String getAvatarUrl(JSONObject userObj) {
JSONArray jsonArray = userObj.getJSONArray("avatar");
if (Objects.isNull(jsonArray) || jsonArray.isEmpty()) {
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..be5c1869b9d1c81d9182a5b3c8e808d09616f8f9
--- /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/AuthPinterestUrlBuilder.java b/src/main/java/me/zhyd/oauth/url/AuthPinterestUrlBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..c3214799daf63c623a354d220da31f712854364c
--- /dev/null
+++ b/src/main/java/me/zhyd/oauth/url/AuthPinterestUrlBuilder.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.PINTEREST;
+
+/**
+ * Pinterest相关的URL构建类
+ *
+ * @author hongwei.peng (pengisgood(at)gmail(dot)com)
+ * @version 1.9.0
+ * @since 1.9.0
+ */
+public class AuthPinterestUrlBuilder extends AuthDefaultUrlBuilder {
+
+ private static final String PINTEREST_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}";
+ private static final String PINTEREST_USER_INFO_PATTERN = "{0}?access_token={1}&fields=id,username,first_name,last_name,bio,image";
+ private static final String PINTEREST_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&state={3}&scope=read_public";
+
+ @Override
+ public String getAccessTokenUrl(String code) {
+ return MessageFormat.format(PINTEREST_ACCESS_TOKEN_PATTERN, PINTEREST.accessToken(), config.getClientId(), config.getClientSecret(), code);
+ }
+
+ @Override
+ public String getUserInfoUrl(AuthUserInfoEntity userInfoEntity) {
+ return MessageFormat.format(PINTEREST_USER_INFO_PATTERN, PINTEREST.userInfo(), userInfoEntity.getAccessToken());
+ }
+
+ @Override
+ public String getAuthorizeUrl() {
+ return MessageFormat.format(PINTEREST_AUTHORIZE_PATTERN, PINTEREST.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/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);
+ }
+}
diff --git a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java
index a5712f8ace4ccb76154ed68d3722019d57134f60..634463f914e619b1ecc3916c765c5f85fbc65f42 100644
--- a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java
+++ b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java
@@ -3,6 +3,7 @@ package me.zhyd.oauth.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpUtil;
import me.zhyd.oauth.exception.AuthException;
import javax.crypto.Mac;
@@ -82,6 +83,7 @@ public class GlobalAuthUtil {
return res;
}
+
public static String parseMapToString(Map params, boolean encode) {
List paramList = new ArrayList<>();
params.forEach((k, v) -> {
@@ -94,6 +96,12 @@ public class GlobalAuthUtil {
});
return CollUtil.join(paramList, "&");
}
+
+ 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)) {
diff --git a/update.md b/update.md
index fe36738df3020f1c978d209bc6b7148d4e4615d3..60e02b7d1d4eae1c7092a5ba8c3b626aabd254ee 100644
--- a/update.md
+++ b/update.md
@@ -1,3 +1,12 @@
+### 2019/07/18
+1. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#19](https://github.com/zhangyd-c/JustAuth/pull/19),集成人人
+2. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#20](https://github.com/zhangyd-c/JustAuth/pull/20),集成Pinterest
+2. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#21](https://github.com/zhangyd-c/JustAuth/pull/21),集成StackOverflow
+
+### 2019/07/17
+1. 优化代码
+2. 集成Teambition登录
+
### 2019/07/16
1. 重构UrlBuilder类
2. 将CSDN相关的类置为`Deprecated`,后续可能会删除,也可能一直保留。毕竟CSDN的openAPI已经不对外开放了。