diff --git a/README.en-US.md b/README.en-US.md
index 6b6ceff4c0ed762a3f1fea6a7d4c1f52662641b3..523798d969334454b823591e6c64b0ec62ab5a30 100644
--- a/README.en-US.md
+++ b/README.en-US.md
@@ -67,6 +67,7 @@
![](https://gitee.com/yadong.zhang/static/raw/master/JustAuth/kujiale.png) |
![](https://gitee.com/yadong.zhang/static/raw/master/JustAuth/gitlab.png) |
![](https://gitee.com/yadong.zhang/static/raw/master/JustAuth/meituan.png) |
+ ![](https://gitee.com/yadong.zhang/static/raw/master/JustAuth/eleme.png) |
@@ -152,6 +153,7 @@ authRequest.login(callback);
|
| [AuthKujialeRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthKujialeRequest.java) | 参考文档 |
|
| [AuthGitlabRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGitlabRequest.java) | 参考文档 |
|
| [AuthMeituanRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthMeituanRequest.java) | 参考文档 |
+|
| [AuthElemeRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthElemeRequest.java) | 参考文档 |
|
| [AuthCsdnRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java) | 无 |
diff --git a/README.md b/README.md
index f59f473decebc915c9531b141db96357abe1604a..a22e10d964b6ddab13e85f0b9176a7fe5f83bcc9 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,7 @@
![](https://gitee.com/yadong.zhang/static/raw/master/JustAuth/kujiale.png) |
![](https://gitee.com/yadong.zhang/static/raw/master/JustAuth/gitlab.png) |
![](https://gitee.com/yadong.zhang/static/raw/master/JustAuth/meituan.png) |
+ ![](https://gitee.com/yadong.zhang/static/raw/master/JustAuth/eleme.png) |
@@ -162,6 +163,7 @@ authRequest.login(callback);
|
| [AuthKujialeRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthKujialeRequest.java) | 参考文档 |
|
| [AuthGitlabRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGitlabRequest.java) | 参考文档 |
|
| [AuthMeituanRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthMeituanRequest.java) | 参考文档 |
+|
| [AuthElemeRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthElemeRequest.java) | 参考文档 |
|
| [AuthCsdnRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java) | 无 |
_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_
diff --git a/docs/README.md b/docs/README.md
index cab35c20ba8dbb4796ee4364dd0c1da836c3572e..a59bc6cbe69d9559360801a0b4f8587c276e1e8a 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -87,10 +87,11 @@ JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具
|
| [AuthStackOverflowRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java) | 参考文档 |
|
| [AuthHuaweiRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthHuaweiRequest.java) | 参考文档 |
|
| [AuthWeChatEnterpriseRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthWeChatEnterpriseRequest.java) | 参考文档 |
-|
| [AuthCsdnRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java) | 无 |
|
| [AuthKujialeRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthKujialeRequest.java) | 参考文档 |
|
| [AuthGitlabRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthGitlabRequest.java) | 参考文档 |
|
| [AuthMeituanRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthMeituanRequest.java) | 参考文档 |
+|
| [AuthElemeRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthElemeRequest.java) | 参考文档 |
+|
| [AuthCsdnRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java) | 无 |
## 快速开始
diff --git a/docs/update.md b/docs/update.md
index 6d30a082e2446aec561d9e109bfa0421ab85f4be..b5e5473f597339925dcfb4e92919226d6e037a87 100644
--- a/docs/update.md
+++ b/docs/update.md
@@ -2,6 +2,7 @@
### 2019/09/06
- 集成“美团”授权登录
+- 集成“饿了么”授权登录
- 升级Fastjson依赖到1.2.60,预防[“Fastjson < 1.2.60 远程拒绝服务漏洞预警”](https://card.weibo.com/article/m/show/id/2309404413257925394542)
## v1.11.0
diff --git a/src/main/java/me/zhyd/oauth/config/AuthSource.java b/src/main/java/me/zhyd/oauth/config/AuthSource.java
index ee0c1898efc286b80f51ac4d70d99c93bbab4210..f4ecf5e4bac515ab4b1b975382e11a5a7e4bc845 100644
--- a/src/main/java/me/zhyd/oauth/config/AuthSource.java
+++ b/src/main/java/me/zhyd/oauth/config/AuthSource.java
@@ -643,6 +643,35 @@ public enum AuthSource {
public String refresh() {
return "https://openapi.waimai.meituan.com/oauth/refresh_token";
}
+ },
+
+ /**
+ * 饿了么
+ *
+ * 注:集成的是正式环境,非沙箱环境
+ *
+ * @since 1.12.0
+ */
+ ELEME {
+ @Override
+ public String authorize() {
+ return "https://open-api.shop.ele.me/authorize";
+ }
+
+ @Override
+ public String accessToken() {
+ return "https://open-api.shop.ele.me/token";
+ }
+
+ @Override
+ public String userInfo() {
+ return "https://open-api.shop.ele.me/api/v1/";
+ }
+
+ @Override
+ public String refresh() {
+ return "https://open-api.shop.ele.me/token";
+ }
};
/**
diff --git a/src/main/java/me/zhyd/oauth/request/AuthElemeRequest.java b/src/main/java/me/zhyd/oauth/request/AuthElemeRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9773e699a138d6f9f1b3e341d500264e3c3a9213
--- /dev/null
+++ b/src/main/java/me/zhyd/oauth/request/AuthElemeRequest.java
@@ -0,0 +1,193 @@
+package me.zhyd.oauth.request;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.alibaba.fastjson.JSONObject;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.config.AuthSource;
+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.GlobalAuthUtil;
+import me.zhyd.oauth.utils.UrlBuilder;
+import me.zhyd.oauth.utils.UuidUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 饿了么
+ *
+ * 注:集成的是正式环境,非沙箱环境
+ *
+ * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
+ * @since 1.12.0
+ */
+public class AuthElemeRequest extends AuthDefaultRequest {
+
+ public AuthElemeRequest(AuthConfig config) {
+ super(config, AuthSource.ELEME);
+ }
+
+ public AuthElemeRequest(AuthConfig config, AuthStateCache authStateCache) {
+ super(config, AuthSource.ELEME, authStateCache);
+ }
+
+ @Override
+ protected AuthToken getAccessToken(AuthCallback authCallback) {
+
+ HttpRequest request = HttpRequest.post(source.accessToken())
+ .form("client_id", config.getClientId())
+ .form("redirect_uri", config.getRedirectUri())
+ .form("code", authCallback.getCode())
+ .form("grant_type", "authorization_code");
+
+ // 设置header
+ this.setHeader(request);
+
+ HttpResponse response = request.execute();
+ JSONObject object = JSONObject.parseObject(response.body());
+
+ this.checkResponse(object);
+
+ return AuthToken.builder()
+ .openId(this.getOpenId(authCallback.getCode()))
+ .accessToken(object.getString("access_token"))
+ .refreshToken(object.getString("refresh_token"))
+ .tokenType(object.getString("token_type"))
+ .expireIn(object.getIntValue("expires_in"))
+ .build();
+ }
+
+ @Override
+ protected AuthUser getUserInfo(AuthToken authToken) {
+ Map parameters = new HashMap<>();
+ // 获取商户账号信息的API接口名称
+ String action = "eleme.user.getUser";
+ // 时间戳,单位秒。API服务端允许客户端请求最大时间误差为正负5分钟。
+ final long timestamp = System.currentTimeMillis();
+ // 公共参数
+ Map metasHashMap = new HashMap();
+ metasHashMap.put("app_key", config.getClientId());
+ metasHashMap.put("timestamp", timestamp);
+ String signature = GlobalAuthUtil.generateElemeSignature(config.getClientId(), config.getClientSecret(), timestamp, action, authToken.getAccessToken(), parameters);
+
+ HttpRequest request = HttpRequest.post(source.userInfo())
+ .form("nop", "1.0.0")
+ .form("id", this.getRequestId())
+ .form("metas", metasHashMap)
+ .form("action", action)
+ .form("token", authToken.getAccessToken())
+ .form("params", parameters)
+ .form("signature", signature);
+
+ // 设置header
+ this.setHeader(request, "application/json; charset=utf-8");
+
+ HttpResponse response = request.execute();
+
+ JSONObject object = JSONObject.parseObject(response.body());
+
+ // 校验请求
+ if (object.containsKey("error")) {
+ throw new AuthException(object.getJSONObject("error").getString("message"));
+ }
+
+ JSONObject result = object.getJSONObject("result");
+
+ return AuthUser.builder()
+ .uuid(result.getString("userId"))
+ .username(result.getString("userName"))
+ .nickname(result.getString("userName"))
+ .gender(AuthUserGender.UNKNOWN)
+ .token(authToken)
+ .source(source)
+ .build();
+ }
+
+ @Override
+ public AuthResponse refresh(AuthToken oldToken) {
+ HttpRequest request = HttpRequest.post(source.refresh())
+ .form("refresh_token", oldToken.getRefreshToken())
+ .form("grant_type", "refresh_token");
+
+ // 设置header
+ this.setHeader(request);
+
+ HttpResponse response = request.execute();
+ JSONObject object = JSONObject.parseObject(response.body());
+
+ this.checkResponse(object);
+
+ return AuthResponse.builder()
+ .code(AuthResponseStatus.SUCCESS.getCode())
+ .data(AuthToken.builder()
+ .accessToken(object.getString("access_token"))
+ .refreshToken(object.getString("refresh_token"))
+ .tokenType(object.getString("token_type"))
+ .expireIn(object.getIntValue("expires_in"))
+ .build())
+ .build();
+ }
+
+ @Override
+ public String authorize(String state) {
+ return UrlBuilder.fromBaseUrl(super.authorize(state))
+ .queryParam("scope", "all")
+ .build();
+ }
+
+ private String getOpenId(String code) {
+ HttpRequest request = HttpRequest.post("https://open-api.shop.ele.me/identity")
+ .form("grant_type", "authorization_code")
+ .form("code", code)
+ .form("redirect_uri", config.getRedirectUri())
+ .form("client_id", config.getClientId());
+
+ // 设置header
+ this.setHeader(request);
+
+ HttpResponse response = request.execute();
+ JSONObject object = JSONObject.parseObject(response.body());
+
+ this.checkResponse(object);
+ return object.getString("openId");
+ }
+
+ private String getBasic(String appKey, String appSecret) {
+ StringBuilder sb = new StringBuilder();
+ String encodeToString = Base64.encode((appKey + ":" + appSecret).getBytes());
+ sb.append("Basic").append(" ").append(encodeToString);
+ return sb.toString();
+ }
+
+ private void setHeader(HttpRequest request) {
+ setHeader(request, "application/x-www-form-urlencoded;charset=UTF-8");
+ }
+
+ private void setHeader(HttpRequest request, String contentType) {
+ request.header("Accept", "text/xml,text/javascript,text/html")
+ .header("Content-Type", contentType)
+ .header("Accept-Encoding", "gzip")
+ .header("User-Agent", "eleme-openapi-java-sdk")
+ .header("x-eleme-requestid", getRequestId())
+ .header("Authorization", this.getBasic(config.getClientId(), config.getClientSecret()));
+ }
+
+ private String getRequestId() {
+ return UuidUtils.getUUID() + "|" + System.currentTimeMillis();
+ }
+
+ private void checkResponse(JSONObject object) {
+ if (object.containsKey("error")) {
+ throw new AuthException(object.getString("error_description"));
+ }
+ }
+
+}
diff --git a/src/main/java/me/zhyd/oauth/request/AuthMeituanRequest.java b/src/main/java/me/zhyd/oauth/request/AuthMeituanRequest.java
index 4ba079c5a460abc039cfce57e285c327afd217fd..7685d2faff39118d063bd33e3775e7cd77450d06 100644
--- a/src/main/java/me/zhyd/oauth/request/AuthMeituanRequest.java
+++ b/src/main/java/me/zhyd/oauth/request/AuthMeituanRequest.java
@@ -74,11 +74,11 @@ public class AuthMeituanRequest extends AuthDefaultRequest {
@Override
public AuthResponse refresh(AuthToken oldToken) {
- HttpResponse response = HttpRequest.post(source.accessToken())
+ HttpResponse response = HttpRequest.post(source.refresh())
.form("app_id", config.getClientId())
.form("secret", config.getClientSecret())
.form("refresh_token", oldToken.getRefreshToken())
- .form("grant_type", "authorization_code")
+ .form("grant_type", "refresh_token")
.execute();
JSONObject object = JSONObject.parseObject(response.body());
diff --git a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java
index e1aad50aa27618118a67bc8d28734828ab5db595..5c248f0be15c353a8fc125d991d6af7d673a9056 100644
--- a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java
+++ b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java
@@ -4,6 +4,7 @@ import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
import me.zhyd.oauth.exception.AuthException;
import javax.crypto.Mac;
@@ -14,6 +15,7 @@ import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
@@ -27,11 +29,25 @@ public class GlobalAuthUtil {
private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;
private static final String ALGORITHM = "HmacSHA256";
+ /**
+ * 生成钉钉请求的Signature
+ *
+ * @param secretKey 平台应用的授权密钥
+ * @param timestamp 时间戳
+ * @return Signature
+ */
public static String generateDingTalkSignature(String secretKey, String timestamp) {
byte[] signData = sign(secretKey.getBytes(DEFAULT_ENCODING), timestamp.getBytes(DEFAULT_ENCODING));
return urlEncode(new String(Base64.encode(signData, false)));
}
+ /**
+ * 签名
+ *
+ * @param key key
+ * @param data data
+ * @return byte[]
+ */
private static byte[] sign(byte[] key, byte[] data) {
try {
Mac mac = Mac.getInstance(ALGORITHM);
@@ -44,11 +60,16 @@ public class GlobalAuthUtil {
}
}
+ /**
+ * 编码
+ *
+ * @param value str
+ * @return encode str
+ */
public static String urlEncode(String value) {
if (value == null) {
return "";
}
-
try {
String encoded = URLEncoder.encode(value, GlobalAuthUtil.DEFAULT_ENCODING.displayName());
return encoded.replace("+", "%20").replace("*", "%2A").replace("~", "%7E").replace("/", "%2F");
@@ -57,6 +78,13 @@ public class GlobalAuthUtil {
}
}
+
+ /**
+ * 解码
+ *
+ * @param value str
+ * @return decode str
+ */
public static String urlDecode(String value) {
if (value == null) {
return "";
@@ -68,6 +96,12 @@ public class GlobalAuthUtil {
}
}
+ /**
+ * string字符串转map,str格式为 {@code xxx=xxx&xxx=xxx}
+ *
+ * @param accessTokenStr 待转换的字符串
+ * @return map
+ */
public static Map parseStringToMap(String accessTokenStr) {
Map res = new HashMap<>();
if (accessTokenStr.contains("&")) {
@@ -82,7 +116,13 @@ public class GlobalAuthUtil {
return res;
}
-
+ /**
+ * map转字符串,转换后的字符串格式为 {@code xxx=xxx&xxx=xxx}
+ *
+ * @param params 待转换的map
+ * @param encode 是否转码
+ * @return str
+ */
public static String parseMapToString(Map params, boolean encode) {
List paramList = new ArrayList<>();
params.forEach((k, v) -> {
@@ -95,13 +135,25 @@ public class GlobalAuthUtil {
});
return CollUtil.join(paramList, "&");
}
-
+
+ /**
+ * 将url的参数列表转换成map
+ *
+ * @param url 待转换的url
+ * @return map
+ */
public static Map parseQueryToMap(String url) {
Map paramMap = new HashMap<>();
HttpUtil.decodeParamMap(url, "UTF-8").forEach(paramMap::put);
return paramMap;
}
+ /**
+ * 是否为http协议
+ *
+ * @param url 待验证的url
+ * @return true: http协议, false: 非http协议
+ */
public static boolean isHttpProtocol(String url) {
if (StringUtils.isEmpty(url)) {
return false;
@@ -109,6 +161,12 @@ public class GlobalAuthUtil {
return url.startsWith("http://");
}
+ /**
+ * 是否为https协议
+ *
+ * @param url 待验证的url
+ * @return true: https协议, false: 非https协议
+ */
public static boolean isHttpsProtocol(String url) {
if (StringUtils.isEmpty(url)) {
return false;
@@ -116,8 +174,68 @@ public class GlobalAuthUtil {
return url.startsWith("https://");
}
+ /**
+ * 是否为本地主机(域名)
+ *
+ * @param url 待验证的url
+ * @return true: 本地主机(域名), false: 非本地主机(域名)
+ */
public static boolean isLocalHost(String url) {
return StringUtils.isEmpty(url) || url.contains("127.0.0.1") || url.contains("localhost");
}
+ /**
+ * 生成饿了么请求的Signature
+ *
+ * 代码copy并修改自:https://coding.net/u/napos_openapi/p/eleme-openapi-java-sdk/git/blob/master/src/main/java/eleme/openapi/sdk/utils/SignatureUtil.java
+ *
+ * @param appKey 平台应用的授权key
+ * @param secret 平台应用的授权密钥
+ * @param timestamp 时间戳,单位秒。API服务端允许客户端请求最大时间误差为正负5分钟。
+ * @param action 饿了么请求的api方法
+ * @param token 用户授权的token
+ * @param parameters 加密参数
+ * @return Signature
+ */
+ public static String generateElemeSignature(String appKey, String secret, long timestamp, String action, String token, Map parameters) {
+ final Map sorted = new TreeMap<>();
+ for (Map.Entry entry : parameters.entrySet()) {
+ sorted.put(entry.getKey(), entry.getValue());
+ }
+ sorted.put("app_key", appKey);
+ sorted.put("timestamp", timestamp);
+ StringBuffer string = new StringBuffer();
+ for (Map.Entry entry : sorted.entrySet()) {
+ string.append(entry.getKey()).append("=").append(JSON.toJSONString(entry.getValue()));
+ }
+ String splice = String.format("%s%s%s%s", action, token, string, secret);
+ String calculatedSignature = md5(splice);
+ return calculatedSignature.toUpperCase();
+ }
+
+ /**
+ * MD5加密饿了么请求的Signature
+ *
+ * 代码copy并修改自:https://coding.net/u/napos_openapi/p/eleme-openapi-java-sdk/git/blob/master/src/main/java/eleme/openapi/sdk/utils/SignatureUtil.java
+ *
+ * @param str 饿了么请求的Signature
+ * @return md5 str
+ */
+ private static String md5(String str) {
+ MessageDigest md = null;
+ StringBuilder buffer = null;
+ try {
+ md = MessageDigest.getInstance("MD5");
+ md.update(str.getBytes(StandardCharsets.UTF_8));
+ byte[] byteData = md.digest();
+ buffer = new StringBuilder();
+ for (byte byteDatum : byteData) {
+ buffer.append(Integer.toString((byteDatum & 0xff) + 0x100, 16).substring(1));
+ }
+ } catch (Exception ignored) {
+ }
+
+ return null == buffer ? "" : buffer.toString();
+ }
+
}