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)