diff --git a/README.md b/README.md
index 90822288d96b3cf0d88455ee7160eacb60e00d96..34142e8cee6fde0ee65c33a4dd99e6a0800863ab 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
-
+
@@ -15,7 +15,7 @@
-
+
@@ -68,7 +68,7 @@ JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具
me.zhyd.oauth
JustAuth
- 1.8.0
+ 1.8.1
```
- 调用api
diff --git a/pom.xml b/pom.xml
index 74425b48a8b16abc2f2f3d0f55fb9d4e3b612624..65a7890ed2c1a852891aa3ad77676ed46521f0a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
me.zhyd.oauth
JustAuth
- 1.8.0
+ 1.8.1
JustAuth
https://gitee.com/yadong.zhang/JustAuth
@@ -54,6 +54,7 @@
4.11
1.2.44
3.7.4.ALL
+ 1.7.25
@@ -84,6 +85,11 @@
${alipay-sdk-version}
compile
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j-version}
+
diff --git a/src/main/java/me/zhyd/oauth/utils/AuthState.java b/src/main/java/me/zhyd/oauth/utils/AuthState.java
new file mode 100644
index 0000000000000000000000000000000000000000..2584f21ccac9a157357c8682b3516c7ba932d09d
--- /dev/null
+++ b/src/main/java/me/zhyd/oauth/utils/AuthState.java
@@ -0,0 +1,165 @@
+package me.zhyd.oauth.utils;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.RandomUtil;
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.request.ResponseStatus;
+
+import java.nio.charset.Charset;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * state工具,负责创建、获取和删除state
+ *
+ * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
+ * @version 1.0
+ * @since 1.8
+ */
+@Slf4j
+public class AuthState {
+
+ /**
+ * 空字符串
+ */
+ private static final String EMPTY_STR = "";
+
+ /**
+ * state存储器
+ */
+ private static ConcurrentHashMap stateBucket = new ConcurrentHashMap<>();
+
+ /**
+ * 生成随机的state
+ *
+ * @param source oauth平台
+ * @return state
+ */
+ public static String create(String source) {
+ return create(source, RandomUtil.randomString(4));
+ }
+
+ /**
+ * 创建state
+ *
+ * @param source oauth平台
+ * @param body 希望加密到state的消息体
+ * @return state
+ */
+ public static String create(String source, Object body) {
+ return create(source, JSON.toJSONString(body));
+ }
+
+ /**
+ * 创建state
+ * state建议格式请参考:https://gitee.com/yadong.zhang/JustAuth/wikis/Q&A?sort_id=1513074#3-%E5%8D%87%E7%BA%A7%E5%88%B0180%E5%90%8E%E5%AF%B9%E4%BA%8Estate%E5%8F%82%E6%95%B0%E6%9C%89%E4%BB%80%E4%B9%88%E7%89%B9%E6%AE%8A%E8%A6%81%E6%B1%82%E5%90%97
+ *
+ * @param source oauth平台
+ * @param body 希望加密到state的消息体
+ * @return state
+ */
+ public static String create(String source, String body) {
+ String currentIp = getCurrentIp();
+ String simpleKey = ((source + currentIp));
+ String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));
+ log.debug("Create the state: ip={}, platform={}, simpleKey={}, key={}, body={}", currentIp, source, simpleKey, key, body);
+
+ if (stateBucket.containsKey(key)) {
+ log.debug("Get from bucket: {}", stateBucket.get(key));
+ return stateBucket.get(key);
+ }
+
+ String simpleState = source + "_" + currentIp + "_" + body;
+ String state = Base64.encode(simpleState.getBytes(Charset.forName("UTF-8")));
+ log.debug("Create a new state: {}", state, simpleState);
+ stateBucket.put(key, state);
+ return state;
+ }
+
+ /**
+ * 获取state
+ *
+ * @param source oauth平台
+ * @return state
+ */
+ public static String get(String source) {
+ String currentIp = getCurrentIp();
+ String simpleKey = ((source + currentIp));
+ String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));
+ log.debug("Get state by the key[{}], current ip[{}]", key, currentIp);
+ return stateBucket.get(key);
+ }
+
+ /**
+ * 获取state中保存的body内容
+ *
+ * @param source oauth平台
+ * @param state 加密后的state
+ * @param clazz body的实际类型
+ * @return state
+ */
+ public static T getBody(String source, String state, Class clazz) {
+ if (StringUtils.isEmpty(state) || null == clazz) {
+ return null;
+ }
+ log.debug("Get body from the state[{}] of the {} and convert it to {}", state, source, clazz.toString());
+ String currentIp = getCurrentIp();
+ String decodedState = Base64.decodeStr(state);
+ log.debug("The decoded state is [{}]", decodedState);
+ if (!decodedState.startsWith(source)) {
+ return null;
+ }
+ String noneSourceState = decodedState.substring(source.length() + 1);
+ if (!noneSourceState.startsWith(currentIp)) {
+ // ip不相同,可能为非法的请求
+ throw new AuthException(ResponseStatus.ILLEGAL_REQUEST);
+ }
+ String body = noneSourceState.substring(currentIp.length() + 1);
+ log.debug("body is [{}]", body);
+ if (clazz == String.class) {
+ return (T) body;
+ }
+ if (clazz == Integer.class) {
+ return (T) Integer.valueOf(Integer.parseInt(body));
+ }
+ if (clazz == Long.class) {
+ return (T) Long.valueOf(Long.parseLong(body));
+ }
+ if (clazz == Short.class) {
+ return (T) Short.valueOf(Short.parseShort(body));
+ }
+ if (clazz == Double.class) {
+ return (T) Double.valueOf(Double.parseDouble(body));
+ }
+ if (clazz == Float.class) {
+ return (T) Float.valueOf(Float.parseFloat(body));
+ }
+ if (clazz == Boolean.class) {
+ return (T) Boolean.valueOf(Boolean.parseBoolean(body));
+ }
+ if (clazz == Byte.class) {
+ return (T) Byte.valueOf(Byte.parseByte(body));
+ }
+ return JSON.parseObject(body, clazz);
+ }
+
+ /**
+ * 登录成功后,清除state
+ *
+ * @param source oauth平台
+ */
+ public static void delete(String source) {
+ String currentIp = getCurrentIp();
+
+ String simpleKey = ((source + currentIp));
+ String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));
+ log.debug("Delete used state[{}] by the key[{}], current ip[{}]", stateBucket.get(key), key, currentIp);
+ stateBucket.remove(key);
+ }
+
+ private static String getCurrentIp() {
+ String currentIp = IpUtils.getIp();
+ return StringUtils.isEmpty(currentIp) ? EMPTY_STR : currentIp;
+ }
+}
diff --git a/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java b/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f49d382109046e8180779d86d1dab4ece3601919
--- /dev/null
+++ b/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java
@@ -0,0 +1,231 @@
+package me.zhyd.oauth.utils;
+
+import cn.hutool.core.date.DatePattern;
+import cn.hutool.core.date.DateUtil;
+import me.zhyd.oauth.config.AuthConfig;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.*;
+
+public class AuthStateTest {
+
+ /**
+ * step1 生成state: 预期创建一个新的state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV9yM3ll
+ *
+ * step2 重复生成state: 预期从bucket中返回一个可用的state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV9yM3ll
+ *
+ * step3 获取state: 预期获取上面生成的state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV9yM3ll
+ *
+ * step4 删除state: 预期删除掉上面创建的state...
+ *
+ * step5 重新获取state: 预期返回null...
+ * null
+ */
+ @Test
+ public void test() {
+ String source = "github";
+ System.out.println("\nstep1 生成state: 预期创建一个新的state...");
+ String state = AuthState.create(source);
+ System.out.println(state);
+
+ System.out.println("\nstep2 重复生成state: 预期从bucket中返回一个可用的state...");
+ String recreateState = AuthState.create(source);
+ System.out.println(recreateState);
+ Assert.assertEquals(state, recreateState);
+
+ System.out.println("\nstep3 获取state: 预期获取上面生成的state...");
+ String stateByBucket = AuthState.get(source);
+ System.out.println(stateByBucket);
+ Assert.assertEquals(state, stateByBucket);
+
+ System.out.println("\nstep4 删除state: 预期删除掉上面创建的state...");
+ AuthState.delete(source);
+
+ System.out.println("\nstep5 重新获取state: 预期返回null...");
+ String deletedState = AuthState.get(source);
+ System.out.println(deletedState);
+ Assert.assertNull(deletedState);
+ }
+
+ /**
+ * 通过随机字符串生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV9wdnAy
+ *
+ * 通过传入自定义的字符串生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV/ov5nmmK/kuIDkuKrlrZfnrKbkuLI=
+ *
+ * 通过传入数字生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV8xMTE=
+ *
+ * 通过传入日期生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV8xNTQ2MzE1OTMyMDAw
+ *
+ * 通过传入map生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV97InVzZXJUb2tlbiI6Inh4eHh4IiwidXNlcklkIjoxfQ==
+ *
+ * 通过传入List生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV9bInh4eHgiLCJ4eHh4eHh4eCJd
+ *
+ * 通过传入实体类生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV97ImNsaWVudElkIjoieHh4eHgiLCJjbGllbnRTZWNyZXQiOiJ4eHh4eCIsInVuaW9uSWQiOmZhbHNlfQ==
+ */
+ @Test
+ public void create() {
+ String source = "github";
+ System.out.println("\n通过随机字符串生成state...");
+ String state = AuthState.create(source);
+ System.out.println(state);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入自定义的字符串生成state...");
+ String stringBody = "这是一个字符串";
+ String stringState = AuthState.create(source, stringBody);
+ System.out.println(stringState);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入数字生成state...");
+ Integer numberBody = 111;
+ String numberState = AuthState.create(source, numberBody);
+ System.out.println(numberState);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入日期生成state...");
+ Date dateBody = DateUtil.parse("2019-01-01 12:12:12", DatePattern.NORM_DATETIME_PATTERN);
+ String dateState = AuthState.create(source, dateBody);
+ System.out.println(dateState);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入map生成state...");
+ Map mapBody = new HashMap<>();
+ mapBody.put("userId", 1);
+ mapBody.put("userToken", "xxxxx");
+ String mapState = AuthState.create(source, mapBody);
+ System.out.println(mapState);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入List生成state...");
+ List listBody = new ArrayList<>();
+ listBody.add("xxxx");
+ listBody.add("xxxxxxxx");
+ String listState = AuthState.create(source, listBody);
+ System.out.println(listState);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入实体类生成state...");
+ AuthConfig entityBody = AuthConfig.builder()
+ .clientId("xxxxx")
+ .clientSecret("xxxxx")
+ .build();
+ String entityState = AuthState.create(source, entityBody);
+ System.out.println(entityState);
+ AuthState.delete(source);
+ }
+
+ /**
+ * 通过随机字符串生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV9kaWNn
+ * dicg
+ *
+ * 通过传入自定义的字符串生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV/ov5nmmK/kuIDkuKrlrZfnrKbkuLI=
+ * 这是一个字符串
+ *
+ * 通过传入数字生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV8xMTE=
+ * 111
+ *
+ * 通过传入日期生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV8xNTQ2MzE1OTMyMDAw
+ * Tue Jan 01 12:12:12 CST 2019
+ *
+ * 通过传入map生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV97InVzZXJUb2tlbiI6Inh4eHh4IiwidXNlcklkIjoxfQ==
+ * {userToken=xxxxx, userId=1}
+ *
+ * 通过传入List生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV9bInh4eHgiLCJ4eHh4eHh4eCJd
+ * [xxxx, xxxxxxxx]
+ *
+ * 通过传入实体类生成state...
+ * Z2l0aHViXzE5Mi4xNjguMTkuMV97ImNsaWVudElkIjoieHh4eHgiLCJjbGllbnRTZWNyZXQiOiJ4eHh4eCIsInVuaW9uSWQiOmZhbHNlfQ==
+ * me.zhyd.oauth.config.AuthConfig@725bef66
+ */
+ @Test
+ public void getBody() {
+ String source = "github";
+ System.out.println("\n通过随机字符串生成state...");
+ String state = AuthState.create(source);
+ System.out.println(state);
+ String body = AuthState.getBody(source, state, String.class);
+ System.out.println(body);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入自定义的字符串生成state...");
+ String stringBody = "这是一个字符串";
+ String stringState = AuthState.create(source, stringBody);
+ System.out.println(stringState);
+ stringBody = AuthState.getBody(source, stringState, String.class);
+ System.out.println(stringBody);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入数字生成state...");
+ Integer numberBody = 111;
+ String numberState = AuthState.create(source, numberBody);
+ System.out.println(numberState);
+ numberBody = AuthState.getBody(source, numberState, Integer.class);
+ System.out.println(numberBody);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入日期生成state...");
+ Date dateBody = DateUtil.parse("2019-01-01 12:12:12", DatePattern.NORM_DATETIME_PATTERN);
+ String dateState = AuthState.create(source, dateBody);
+ System.out.println(dateState);
+ dateBody = AuthState.getBody(source, dateState, Date.class);
+ System.out.println(dateBody);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入map生成state...");
+ Map mapBody = new HashMap<>();
+ mapBody.put("userId", 1);
+ mapBody.put("userToken", "xxxxx");
+ String mapState = AuthState.create(source, mapBody);
+ System.out.println(mapState);
+ mapBody = AuthState.getBody(source, mapState, Map.class);
+ System.out.println(mapBody);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入List生成state...");
+ List listBody = new ArrayList<>();
+ listBody.add("xxxx");
+ listBody.add("xxxxxxxx");
+ String listState = AuthState.create(source, listBody);
+ System.out.println(listState);
+ listBody = AuthState.getBody(source, listState, List.class);
+ System.out.println(listBody);
+ AuthState.delete(source);
+
+ System.out.println("\n通过传入实体类生成state...");
+ AuthConfig entityBody = AuthConfig.builder()
+ .clientId("xxxxx")
+ .clientSecret("xxxxx")
+ .build();
+ String entityState = AuthState.create(source, entityBody);
+ System.out.println(entityState);
+ entityBody = AuthState.getBody(source, entityState, AuthConfig.class);
+ System.out.println(entityBody);
+ AuthState.delete(source);
+ }
+
+ @Test
+ public void getErrorStateBody() {
+ String source = "github";
+ String state = "1111111111111111111111111111111";
+ String body = AuthState.getBody(source, state, String.class);
+ System.out.println(body);
+ AuthState.delete(source);
+ }
+}
\ No newline at end of file
diff --git a/update.md b/update.md
index 6f3069bc0a6663633819ae047cea44f078bd4220..2b04ff0b24e543a8b577d66b0fd420a953f53588 100644
--- a/update.md
+++ b/update.md
@@ -1,3 +1,6 @@
+### 2019/07/15
+1. 新增 `AuthState` 类,内置默认的state生成规则和校验规则
+
### 2019/07/12
1. 合并[Braavos96](https://github.com/Braavos96)提交的[PR#16](https://github.com/zhangyd-c/JustAuth/pull/16)