diff --git a/README.md b/README.md index cde29716b7e25d1253aa70e454c0ff3ddced27d1..a410b1140a8d23bdbacf3fed61b8f3c433fd3768 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

- + @@ -15,7 +15,7 @@ - +

@@ -76,7 +76,7 @@ JustAuth,如你所见,它仅仅是一个**第三方授权登录**的**工具 me.zhyd.oauth JustAuth - 1.9.2 + 1.9.3 ``` - 调用api @@ -91,14 +91,19 @@ AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder() // 生成授权页面 authRequest.authorize(); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的参数 +// 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 +// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state authRequest.login(callback); ``` -注:`1.8.0`版本后,增加了`state`参数校验,用于防止[CSRF](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0)。强烈建议,保证单次流程内`state`的唯一性,且每个`state`只可用一次。 - **配套Demo**: - [Springboot版](https://gitee.com/yadong.zhang/JustAuth-demo) -- [jFinal版](https://github.com/zhangyd-c/jfinal-justauth-demo) +- [jFinal版](https://github.com/xkcoding/jfinal-justauth-demo) +- [ActFramework版](https://github.com/xkcoding/act-justauth-demo) + +**扩展工具** + +- [justauth-spring-boot-starter](https://github.com/xkcoding/justauth-spring-boot-starter): Spring Boot 集成 JustAuth 的最佳实践 **配套SpringBoot starter**: diff --git a/pom.xml b/pom.xml index 975215164f665be95681ff2dc7c1ebd8081c6529..e466e7d2dde182f7f1abf4b43d1cd5a86847bc6f 100644 --- a/pom.xml +++ b/pom.xml @@ -54,7 +54,7 @@ 2.2.1 3.7.0 true - 4.5.15 + 4.6.0 1.18.4 4.11 1.2.58 diff --git a/src/main/java/me/zhyd/oauth/cache/AuthCache.java b/src/main/java/me/zhyd/oauth/cache/AuthCache.java new file mode 100644 index 0000000000000000000000000000000000000000..73de59930ef9ba3ca1439cb4cb4b80a3b8a57107 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/cache/AuthCache.java @@ -0,0 +1,50 @@ +package me.zhyd.oauth.cache; + +/** + * JustAuth缓存,用来缓存State + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @since 1.9.3 + */ +public interface AuthCache { + + /** + * 设置缓存 + * + * @param key 缓存KEY + * @param value 缓存内容 + */ + void set(String key, String value); + + /** + * 设置缓存,指定过期时间 + * + * @param key 缓存KEY + * @param value 缓存内容 + * @param timeout 指定缓存过期时间(毫秒) + */ + void set(String key, String value, long timeout); + + /** + * 获取缓存 + * + * @param key 缓存KEY + * @return 缓存内容 + */ + String get(String key); + + /** + * 是否存在key,如果对应key的value值已过期,也返回false + * + * @param key 缓存KEY + * @return true:存在key,并且value没过期;false:key不存在或者已过期 + */ + boolean containsKey(String key); + + /** + * 清理过期的缓存 + */ + default void pruneCache() { + } + +} diff --git a/src/main/java/me/zhyd/oauth/cache/AuthCacheScheduler.java b/src/main/java/me/zhyd/oauth/cache/AuthCacheScheduler.java new file mode 100644 index 0000000000000000000000000000000000000000..fbdfa88783fc6384b686695500a47900d139b214 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/cache/AuthCacheScheduler.java @@ -0,0 +1,39 @@ +package me.zhyd.oauth.cache; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 缓存调度器 + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @since 1.9.3 + */ +public enum AuthCacheScheduler { + + INSTANCE; + + private AtomicInteger cacheTaskNumber = new AtomicInteger(1); + private ScheduledExecutorService scheduler; + + AuthCacheScheduler() { + create(); + } + + private void create() { + this.shutdown(); + this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("JustAuth-Task-%s", cacheTaskNumber.getAndIncrement()))); + } + + private void shutdown() { + if (null != scheduler) { + this.scheduler.shutdown(); + } + } + + public void schedule(Runnable task, long delay) { + this.scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS); + } +} diff --git a/src/main/java/me/zhyd/oauth/cache/AuthDefaultCache.java b/src/main/java/me/zhyd/oauth/cache/AuthDefaultCache.java new file mode 100644 index 0000000000000000000000000000000000000000..6da66958a7f27c289d92110a40f993e00f44bae8 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/cache/AuthDefaultCache.java @@ -0,0 +1,144 @@ +package me.zhyd.oauth.cache; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * 默认的缓存实现 + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @since 1.9.3 + */ +public class AuthDefaultCache implements AuthCache { + + /** + * 默认缓存过期时间:3分钟 + * 鉴于授权过程中,根据个人的操作习惯,或者授权平台的不同(google等),每个授权流程的耗时也有差异,不过单个授权流程一般不会太长 + * 本缓存工具默认的过期时间设置为3分钟,即程序默认认为3分钟内的授权有效,超过3分钟则默认失效,失效后删除 + */ + private static final long DEF_TIMEOUT = 3 * 60 * 1000; + /** + * state cache + */ + private static Map stateCache = new ConcurrentHashMap<>(); + private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(true); + private final Lock writeLock = cacheLock.writeLock(); + private final Lock readLock = cacheLock.readLock(); + + public AuthDefaultCache() { + this.schedulePrune(DEF_TIMEOUT); + } + + /** + * 设置缓存 + * + * @param key 缓存KEY + * @param value 缓存内容 + */ + @Override + public void set(String key, String value) { + set(key, value, DEF_TIMEOUT); + } + + /** + * 设置缓存 + * + * @param key 缓存KEY + * @param value 缓存内容 + * @param timeout 指定缓存过期时间(毫秒) + */ + @Override + public void set(String key, String value, long timeout) { + writeLock.lock(); + try { + stateCache.put(key, new CacheState(value, timeout)); + } finally { + writeLock.unlock(); + } + } + + /** + * 获取缓存 + * + * @param key 缓存KEY + * @return 缓存内容 + */ + @Override + public String get(String key) { + readLock.lock(); + try { + CacheState cacheState = stateCache.get(key); + if (null == cacheState || cacheState.isExpired()) { + return null; + } + return cacheState.getState(); + } finally { + readLock.unlock(); + } + } + + /** + * 是否存在key,如果对应key的value值已过期,也返回false + * + * @param key 缓存KEY + * @return true:存在key,并且value没过期;false:key不存在或者已过期 + */ + @Override + public boolean containsKey(String key) { + readLock.lock(); + try { + CacheState cacheState = stateCache.get(key); + return null != cacheState && !cacheState.isExpired(); + } finally { + readLock.unlock(); + } + } + + /** + * 清理过期的缓存 + */ + @Override + public void pruneCache() { + Iterator values = stateCache.values().iterator(); + CacheState cacheState; + while (values.hasNext()) { + cacheState = values.next(); + if (cacheState.isExpired()) { + values.remove(); + } + } + } + + /** + * 定时清理 + * + * @param delay 间隔时长,单位毫秒 + */ + public void schedulePrune(long delay) { + AuthCacheScheduler.INSTANCE.schedule(this::pruneCache, delay); + } + + @Getter + @Setter + private class CacheState implements Serializable { + private String state; + private long expire; + + CacheState(String state, long expire) { + this.state = state; + // 实际过期时间等于当前时间加上有效期 + this.expire = System.currentTimeMillis() + expire; + } + + boolean isExpired() { + return System.currentTimeMillis() > this.expire; + } + } +} diff --git a/src/main/java/me/zhyd/oauth/cache/AuthStateCache.java b/src/main/java/me/zhyd/oauth/cache/AuthStateCache.java new file mode 100644 index 0000000000000000000000000000000000000000..e667829dbd8ed7cf63cfc7e3f7b81870fe2b9ccf --- /dev/null +++ b/src/main/java/me/zhyd/oauth/cache/AuthStateCache.java @@ -0,0 +1,51 @@ +package me.zhyd.oauth.cache; + +/** + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0 + * @since 1.8 + */ +public class AuthStateCache { + private static AuthCache authCache = new AuthDefaultCache(); + + /** + * 存入缓存 + * + * @param key 缓存key + * @param value 缓存内容 + */ + public static void cache(String key, String value) { + authCache.set(key, value); + } + + /** + * 存入缓存 + * + * @param key 缓存key + * @param value 缓存内容 + * @param timeout 指定缓存过期时间(毫秒) + */ + public static void cache(String key, String value, long timeout) { + authCache.set(key, value, timeout); + } + + /** + * 获取缓存内容 + * + * @param key 缓存key + * @return 缓存内容 + */ + public static String get(String key) { + return authCache.get(key); + } + + /** + * 是否存在key,如果对应key的value值已过期,也返回false + * + * @param key 缓存key + * @return true:存在key,并且value没过期;false:key不存在或者已过期 + */ + public static boolean containsKey(String key) { + return authCache.containsKey(key); + } +} diff --git a/src/main/java/me/zhyd/oauth/config/AuthConfig.java b/src/main/java/me/zhyd/oauth/config/AuthConfig.java index 6e72e826dda11874f22900392e04418615296074..deada8904ac748d5b980696eb5cfe3a229ab07b6 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthConfig.java +++ b/src/main/java/me/zhyd/oauth/config/AuthConfig.java @@ -6,7 +6,6 @@ import lombok.*; * JustAuth配置类 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.8 */ @Getter @@ -45,13 +44,6 @@ public class AuthConfig { */ private boolean unionId; - /** - * 一个神奇的参数,最好使用随机的不可测的内容,可以用来防止CSRF攻击 - *

- * 1.8.0版本新增参数 - */ - private String state; - /** * Stack Overflow Key *

diff --git a/src/main/java/me/zhyd/oauth/config/AuthSource.java b/src/main/java/me/zhyd/oauth/config/AuthSource.java index 1ea6704427bfa47502506e8c6cf626559ec6b1e9..a9a0a617ab392c430d309be62da857986c27851d 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthSource.java +++ b/src/main/java/me/zhyd/oauth/config/AuthSource.java @@ -7,7 +7,6 @@ import me.zhyd.oauth.model.AuthResponseStatus; * 各api需要的url, 用枚举类分平台类型管理 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.0 */ public enum AuthSource { diff --git a/src/main/java/me/zhyd/oauth/enums/AuthToutiaoErrorCode.java b/src/main/java/me/zhyd/oauth/enums/AuthToutiaoErrorCode.java index 11007b257ca620fdbbbf507ea728c72688ec8cb4..18df6eeb8833303faba215b1fbddbf25f35ec733 100644 --- a/src/main/java/me/zhyd/oauth/enums/AuthToutiaoErrorCode.java +++ b/src/main/java/me/zhyd/oauth/enums/AuthToutiaoErrorCode.java @@ -7,7 +7,6 @@ import lombok.Getter; * 今日头条授权登录时的异常状态码 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.8 */ @Getter diff --git a/src/main/java/me/zhyd/oauth/enums/AuthUserGender.java b/src/main/java/me/zhyd/oauth/enums/AuthUserGender.java index 3e39e3df57251c2355b8ef936be475c9ea894589..4a4d4021789341b5b212c3fb7f9175ec6d6664f5 100644 --- a/src/main/java/me/zhyd/oauth/enums/AuthUserGender.java +++ b/src/main/java/me/zhyd/oauth/enums/AuthUserGender.java @@ -9,7 +9,6 @@ import java.util.Arrays; * 用户性别 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.8 */ @Getter diff --git a/src/main/java/me/zhyd/oauth/exception/AuthException.java b/src/main/java/me/zhyd/oauth/exception/AuthException.java index f4f7473c8790f0d8daa240ed46ff8b6ce71eae3e..c64b0f865e4053ed0c5ad31eb25375b1512c4c83 100644 --- a/src/main/java/me/zhyd/oauth/exception/AuthException.java +++ b/src/main/java/me/zhyd/oauth/exception/AuthException.java @@ -4,7 +4,6 @@ import me.zhyd.oauth.model.AuthResponseStatus; /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.8 */ public class AuthException extends RuntimeException { diff --git a/src/main/java/me/zhyd/oauth/model/AuthCallback.java b/src/main/java/me/zhyd/oauth/model/AuthCallback.java index fbc08edf54cba9c32d205464711cdbefba05281d..4a6fbeefc416a5feaa4a1b8a9aaa642a229b1768 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthCallback.java +++ b/src/main/java/me/zhyd/oauth/model/AuthCallback.java @@ -2,13 +2,13 @@ package me.zhyd.oauth.model; import lombok.Getter; import lombok.Setter; +import me.zhyd.oauth.cache.AuthStateCache; /** * 授权回调时的参数类 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.8.0 */ @Getter @Setter @@ -28,4 +28,14 @@ public class AuthCallback { * 访问AuthorizeUrl后回调时带的参数state,用于和请求AuthorizeUrl前的state比较,防止CSRF攻击 */ private String state; + + /** + * 内置的检验state合法性的方法 + * + * @return true: state正常;false:state不正常,可能授权时间过长导致state失效 + * @since 1.9.3 + */ + public boolean checkState() { + return AuthStateCache.containsKey(this.state); + } } diff --git a/src/main/java/me/zhyd/oauth/model/AuthResponse.java b/src/main/java/me/zhyd/oauth/model/AuthResponse.java index 484a743683373cf1a6f3ee4c394444826ec47891..3d682d5cff0c5d4430ddbcb1375e88246d96ff67 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthResponse.java +++ b/src/main/java/me/zhyd/oauth/model/AuthResponse.java @@ -8,7 +8,6 @@ import lombok.Setter; * JustAuth统一授权响应类 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.8 */ @Getter diff --git a/src/main/java/me/zhyd/oauth/model/AuthResponseStatus.java b/src/main/java/me/zhyd/oauth/model/AuthResponseStatus.java index 21ca6f63da2664b0eddb8b3cd32df3bcb803fb64..28247f9fda13728c122cdecdb6e82c48a9bcac72 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthResponseStatus.java +++ b/src/main/java/me/zhyd/oauth/model/AuthResponseStatus.java @@ -5,7 +5,6 @@ import lombok.Getter; /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.8 */ @Getter diff --git a/src/main/java/me/zhyd/oauth/model/AuthToken.java b/src/main/java/me/zhyd/oauth/model/AuthToken.java index 472d3d667a8f7ac3f1d856872b1e85659826a294..805a196c3c93b2149c07c5c44265e3d998fedea7 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthToken.java +++ b/src/main/java/me/zhyd/oauth/model/AuthToken.java @@ -9,7 +9,6 @@ import lombok.Setter; * 授权所需的token * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.8 */ @Getter diff --git a/src/main/java/me/zhyd/oauth/model/AuthUser.java b/src/main/java/me/zhyd/oauth/model/AuthUser.java index ad641299ad3956080064c8def7ca00b9e7f2bec5..d98e7272a1560add3a7a4ffd28c96e2c1714d537 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthUser.java +++ b/src/main/java/me/zhyd/oauth/model/AuthUser.java @@ -10,7 +10,6 @@ import me.zhyd.oauth.enums.AuthUserGender; * 授权成功后的用户信息,根据授权平台的不同,获取的数据完整性也不同 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.8 */ @Getter @@ -19,6 +18,8 @@ import me.zhyd.oauth.enums.AuthUserGender; public class AuthUser { /** * 用户第三方系统的唯一id。在调用方集成改组件时,可以用uuid + source唯一确定一个用户 + * + * @since 1.3.3 */ private String uuid; /** diff --git a/src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java b/src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java index 07ce7da703a57a1aa77c681990c609d1c8a6dc28..5f8fbbf1a657bfd14d9ee5dad60f514e523f67dd 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java @@ -21,8 +21,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 支付宝登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.1 */ public class AuthAlipayRequest extends AuthDefaultRequest { @@ -86,17 +85,19 @@ public class AuthAlipayRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("app_id", config.getClientId()) .queryParam("scope", "auth_user") .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) + .queryParam("state", getRealState(state)) .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java b/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java index 5ce2149a1ebdd3c4f4fa4314e8fbdf12f203f1cb..43796fe3be23235c52547ec4b6ee1a192f957109 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java @@ -15,8 +15,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 百度账号登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class AuthBaiduRequest extends AuthDefaultRequest { @@ -79,18 +78,20 @@ public class AuthBaiduRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) .queryParam("display", "popup") - .queryParam("state", getRealState(config.getState())) + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthCodingRequest.java b/src/main/java/me/zhyd/oauth/request/AuthCodingRequest.java index 98e45bfdbec639cdce1631408c0b5c3cf4ff35ac..0ff52416e0c5906d1a3d13c6b715afa3b7f477de 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthCodingRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthCodingRequest.java @@ -4,19 +4,18 @@ import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; 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.enums.AuthUserGender; import me.zhyd.oauth.utils.UrlBuilder; /** * Cooding登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class AuthCodingRequest extends AuthDefaultRequest { @@ -71,18 +70,20 @@ public class AuthCodingRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) .queryParam("scope", "user") - .queryParam("state", getRealState(config.getState())) + .queryParam("state", getRealState(state)) .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java b/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java index 3a72d3701643900d9dd727d790aefff508186ab7..a6c3776fadc19746b3989a4d39597e4bb8bcabc7 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java @@ -14,8 +14,7 @@ import me.zhyd.oauth.model.AuthUser; * CSDN登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ @Deprecated public class AuthCsdnRequest extends AuthDefaultRequest { diff --git a/src/main/java/me/zhyd/oauth/request/AuthDefaultRequest.java b/src/main/java/me/zhyd/oauth/request/AuthDefaultRequest.java index 1774a159d1a3b2f439878650c2386d1a0cce7ec9..354e3c5270344e8fab3b55c027c6deef95369f69 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthDefaultRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthDefaultRequest.java @@ -2,8 +2,8 @@ package me.zhyd.oauth.request; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; -import lombok.Data; import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.cache.AuthStateCache; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.exception.AuthException; @@ -11,14 +11,14 @@ import me.zhyd.oauth.model.*; import me.zhyd.oauth.utils.AuthChecker; import me.zhyd.oauth.utils.StringUtils; import me.zhyd.oauth.utils.UrlBuilder; +import me.zhyd.oauth.utils.UuidUtils; /** * 默认的request处理类 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yangkai.shen (https://xkcoding.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ @Slf4j public abstract class AuthDefaultRequest implements AuthRequest { @@ -43,7 +43,6 @@ public abstract class AuthDefaultRequest implements AuthRequest { public AuthResponse login(AuthCallback authCallback) { try { AuthChecker.checkCode(source == AuthSource.ALIPAY ? authCallback.getAuth_code() : authCallback.getCode()); - AuthChecker.checkState(authCallback.getState(), config.getState()); AuthToken authToken = this.getAccessToken(authCallback); AuthUser user = this.getUserInfo(authToken); @@ -63,17 +62,34 @@ public abstract class AuthDefaultRequest implements AuthRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回授权url,可自行跳转页面 + *

+ * 不建议使用该方式获取授权地址,不带{@code state}的授权地址,容易受到csrf攻击。 + * 建议使用{@link AuthDefaultRequest#authorize(String)}方法生成授权地址,在回调方法中对{@code state}进行校验 * * @return 返回授权地址 + * @see AuthDefaultRequest#authorize(String) */ + @Deprecated @Override public String authorize() { + return this.authorize(null); + } + + /** + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} + * + * @param state state 验证授权流程的参数,可以防止csrf + * @return 返回授权地址 + * @since 1.9.3 + */ + @Override + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) + .queryParam("state", getRealState(state)) .build(); } @@ -130,13 +146,18 @@ public abstract class AuthDefaultRequest implements AuthRequest { } /** - * 获取state,如果为空, 则默认去当前日期的时间戳 + * 获取state,如果为空, 则默认取当前日期的时间戳 * * @param state 原始的state * @return 返回不为null的state */ protected String getRealState(String state) { - return StringUtils.isEmpty(state) ? String.valueOf(System.currentTimeMillis()) : state; + if (StringUtils.isEmpty(state)) { + state = UuidUtils.getUUID(); + } + // 缓存state + AuthStateCache.cache(state, state); + return state; } /** @@ -165,6 +186,7 @@ public abstract class AuthDefaultRequest implements AuthRequest { * @param authToken token封装 * @return HttpResponse */ + @Deprecated protected HttpResponse doPostUserInfo(AuthToken authToken) { return HttpRequest.post(userInfoUrl(authToken)).execute(); } @@ -184,7 +206,9 @@ public abstract class AuthDefaultRequest implements AuthRequest { * * @param authToken token封装 * @return HttpResponse + * @since */ + @Deprecated protected HttpResponse doPostRevoke(AuthToken authToken) { return HttpRequest.post(revokeUrl(authToken)).execute(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java b/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java index ce0f52fdde8ef4be657b9cd660edb7c425ea4ab8..b01eea5396f4a5943094959986516ee2a8971299 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java @@ -18,8 +18,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 钉钉登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class AuthDingTalkRequest extends AuthDefaultRequest { @@ -58,18 +57,20 @@ public class AuthDingTalkRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("appid", config.getClientId()) .queryParam("scope", "snsapi_login") .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java b/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java index a68f232291330759c870c35570926f7bbdb517f1..cdab6aeac58a68356d792f50c99f817862717eaa 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java @@ -15,8 +15,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 抖音登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.4.0 */ public class AuthDouyinRequest extends AuthDefaultRequest { @@ -89,18 +88,20 @@ public class AuthDouyinRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_key", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) .queryParam("scope", "user_info") + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthFacebookRequest.java b/src/main/java/me/zhyd/oauth/request/AuthFacebookRequest.java index 6d7cc2bab621106c0d703561791df855f9d2b241..96e0463cadfc222319bf796e63447e21ec486f33 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthFacebookRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthFacebookRequest.java @@ -15,8 +15,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * Facebook登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.3.0 */ public class AuthFacebookRequest extends AuthDefaultRequest { diff --git a/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java b/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java index e32c1243e3107de906cd4e6e33221ea03d35667c..819e96c13068d4577b175716038099ce0ee15656 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java @@ -14,8 +14,7 @@ import me.zhyd.oauth.model.AuthUser; * Gitee登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class AuthGiteeRequest extends AuthDefaultRequest { diff --git a/src/main/java/me/zhyd/oauth/request/AuthGithubRequest.java b/src/main/java/me/zhyd/oauth/request/AuthGithubRequest.java index f71378ced50992fc05c9ccba06b15ccaed3c5035..00d98798512f13286238175731d4f6b1673598a6 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthGithubRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthGithubRequest.java @@ -17,8 +17,7 @@ import java.util.Map; * Github登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class AuthGithubRequest extends AuthDefaultRequest { @@ -63,12 +62,4 @@ public class AuthGithubRequest extends AuthDefaultRequest { .build(); } - /** - * 检查响应内容是否正确 - * - * @param object 请求响应内容 - */ - private void checkResponse(JSONObject object) { - - } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthGoogleRequest.java b/src/main/java/me/zhyd/oauth/request/AuthGoogleRequest.java index 61b4f7fba2c0bf88139e33ccf5a38e86c2b9d281..8af2c415e020f86cb94a52d760f9327add044e7a 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthGoogleRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthGoogleRequest.java @@ -16,8 +16,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * Google登录 * * @author yangkai.shen (https://xkcoding.com) - * @version 1.3 - * @since 1.3 + * @since 1.3.0 */ public class AuthGoogleRequest extends AuthDefaultRequest { @@ -61,19 +60,20 @@ public class AuthGoogleRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 - * https://openidconnect.googleapis.com/v1/userinfo + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("scope", "openid%20email%20profile") .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java b/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java index e22a7417c0360f7ecdae406a4ba3206b89bd4612..adbb7e67078302f2c7544df822be5ddff387b2bd 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java @@ -18,8 +18,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 领英登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.4.0 */ public class AuthLinkedinRequest extends AuthDefaultRequest { @@ -182,18 +181,20 @@ public class AuthLinkedinRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) .queryParam("scope", "r_liteprofile%20r_emailaddress%20w_member_social") + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java b/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java index 0ac9e00882d8a718cafc85e06e6caebf438b6b42..3b241e91c96ea11bb98d5c3d4ca22a23bfdb3a55 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java @@ -18,8 +18,7 @@ import java.text.MessageFormat; * 小米登录 * * @author yangkai.shen (https://xkcoding.com) - * @version 1.5 - * @since 1.5 + * @since 1.5.0 */ @Slf4j public class AuthMiRequest extends AuthDefaultRequest { @@ -109,19 +108,21 @@ public class AuthMiRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) .queryParam("scope", "user/profile%20user/openIdV2%20user/phoneAndEmail") .queryParam("skip_confirm", "false") + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java b/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java index addd187ede4c37328f6e7896b8b0ccb879abc795..55227afb2e31ba53c6a89eded8bcc557f50298b2 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java @@ -16,8 +16,7 @@ import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap; * 微软登录 * * @author yangkai.shen (https://xkcoding.com) - * @version 1.5 - * @since 1.5 + * @since 1.5.0 */ public class AuthMicrosoftRequest extends AuthDefaultRequest { public AuthMicrosoftRequest(AuthConfig config) { @@ -102,19 +101,21 @@ public class AuthMicrosoftRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) .queryParam("response_mode", "query") .queryParam("scope", "offline_access%20user.read%20mail.read") - .queryParam("state", getRealState(config.getState())) + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java b/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java index 58cc74367bb3dc819e3cd2639387a4ff1115e19a..c67819dcdbac44a5aa621f29d5b61d542cf5a31f 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java @@ -15,8 +15,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * oschina登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class AuthOschinaRequest extends AuthDefaultRequest { @@ -59,7 +58,7 @@ public class AuthOschinaRequest extends AuthDefaultRequest { /** * 返回获取accessToken的url * - * @param code + * @param code 授权回调时带回的授权码 * @return 返回获取accessToken的url */ @Override diff --git a/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java b/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java index a8f5c3835e3348196ef8ee3b8fe4d69bf2f08545..31151d51e07ee37807f41b6e7b8ece2070762484 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java @@ -19,8 +19,7 @@ import static me.zhyd.oauth.config.AuthSource.PINTEREST; * Pinterest登录 * * @author hongwei.peng (pengisgood(at)gmail(dot)com) - * @version 1.9.0 - * @since 1.8 + * @since 1.9.0 */ public class AuthPinterestRequest extends AuthDefaultRequest { @@ -69,14 +68,21 @@ public class AuthPinterestRequest extends AuthDefaultRequest { return jsonObject.getJSONObject("60x60").getString("url"); } + /** + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} + * + * @param state state 验证授权流程的参数,可以防止csrf + * @return 返回授权地址 + * @since 1.9.3 + */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) .queryParam("scope", "read_public") + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthQqRequest.java b/src/main/java/me/zhyd/oauth/request/AuthQqRequest.java index 1b8d6f9182891923b4cef3c2111d1fb18c3b4a37..274ccc24f405e37c04fe78ad4a62b9c768a4e0c2 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthQqRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthQqRequest.java @@ -20,8 +20,7 @@ import java.util.Map; * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @author yangkai.shen (https://xkcoding.com) - * @version 1.0 - * @since 1.8 + * @since 1.1.0 */ public class AuthQqRequest extends AuthDefaultRequest { public AuthQqRequest(AuthConfig config) { @@ -69,6 +68,13 @@ public class AuthQqRequest extends AuthDefaultRequest { .build(); } + /** + * 获取QQ用户的OpenId,支持自定义是否启用查询unionid的功能,如果启用查询unionid的功能, + * 那就需要调用者先通过邮件申请unionid功能,参考链接 {@see http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D} + * + * @param authToken 通过{@link AuthQqRequest#getAccessToken(AuthCallback)}获取到的{@code authToken} + * @return openId + */ private String getOpenId(AuthToken authToken) { HttpResponse response = HttpRequest.get(UrlBuilder.fromBaseUrl("https://graph.qq.com/oauth2.0/me") .queryParam("access_token", authToken.getAccessToken()) diff --git a/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java b/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java index 6888764dc40b2737ce31cfce93819562b3509a54..4b1186fd9114d2e0575b7ed4ebb4047f11762221 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java @@ -19,8 +19,7 @@ import static me.zhyd.oauth.model.AuthResponseStatus.SUCCESS; * 人人登录 * * @author hongwei.peng (pengisgood(at)gmail(dot)com) - * @version 1.9.0 - * @since 1.8 + * @since 1.9.0 */ public class AuthRenrenRequest extends AuthDefaultRequest { diff --git a/src/main/java/me/zhyd/oauth/request/AuthRequest.java b/src/main/java/me/zhyd/oauth/request/AuthRequest.java index d06913ccbbd466d9ed8b19e73200d285bdc4602b..6ceca1ee70b34f95e3d12ccca19b243f5a79c7b6 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthRequest.java @@ -8,20 +8,33 @@ import me.zhyd.oauth.model.AuthToken; /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 * @since 1.8 */ public interface AuthRequest { /** - * 返回认证url,可自行跳转页面 + * 返回授权url,可自行跳转页面 + *

+ * 不建议使用该方式获取授权地址,不带{@code state}的授权地址,容易受到csrf攻击。 + * 建议使用{@link AuthDefaultRequest#authorize(String)}方法生成授权地址,在回调方法中对{@code state}进行校验 * * @return 返回授权地址 */ + @Deprecated default String authorize() { throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED); } + /** + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} + * + * @param state state 验证授权流程的参数,可以防止csrf + * @return 返回授权地址 + */ + default String authorize(String state) { + throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED); + } + /** * 第三方登录 * diff --git a/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java b/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java index c23439e75a48e6b4cbfb1bb9ba7c6b5b9121065a..ab484533392102cd1060adefedbf982e361b5cd3 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java @@ -18,8 +18,7 @@ import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap; * Stack Overflow登录 * * @author hongwei.peng (pengisgood(at)gmail(dot)com) - * @version 1.9.0 - * @since 1.8 + * @since 1.9.0 */ public class AuthStackOverflowRequest extends AuthDefaultRequest { @@ -67,14 +66,21 @@ public class AuthStackOverflowRequest extends AuthDefaultRequest { .build(); } + /** + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} + * + * @param state state 验证授权流程的参数,可以防止csrf + * @return 返回授权地址 + * @since 1.9.3 + */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) .queryParam("scope", "read_inbox") + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthTaobaoRequest.java b/src/main/java/me/zhyd/oauth/request/AuthTaobaoRequest.java index 7a3b522eadbbbddabf848b03a66db274564a7a82..3fcdfdfe490e5b7fa556fe10ebc9ca90a79c5458 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthTaobaoRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthTaobaoRequest.java @@ -4,11 +4,11 @@ import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; 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.enums.AuthUserGender; import me.zhyd.oauth.utils.GlobalAuthUtil; import me.zhyd.oauth.utils.UrlBuilder; @@ -16,8 +16,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 淘宝登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.1.0 */ public class AuthTaobaoRequest extends AuthDefaultRequest { @@ -55,18 +54,20 @@ public class AuthTaobaoRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) .queryParam("view", "web") + .queryParam("state", getRealState(state)) .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java b/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java index d8e79f6068ef28d0e6f1d76e07638fa98aa5e5ce..50c1b7f35621eecfc74574d64afe4e9e27eca1d2 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java @@ -13,8 +13,7 @@ import me.zhyd.oauth.model.*; * Teambition授权登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.9.0 */ public class AuthTeambitionRequest extends AuthDefaultRequest { diff --git a/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java b/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java index 7aaa77dc1d497812d4543508339a7abd5eac9519..7401df8d13c8eb9e07642eb752e8eb31ae35019b 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java @@ -15,8 +15,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 腾讯云登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class AuthTencentCloudRequest extends AuthDefaultRequest { @@ -71,18 +70,20 @@ public class AuthTencentCloudRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_id", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) .queryParam("scope", "user") - .queryParam("state", getRealState(config.getState())) + .queryParam("state", getRealState(state)) .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java b/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java index 2a1c9790f0f85e7329bfbfc949f1449dff05a0cf..89926f360b7e54d73a80314049c38f1411632a0d 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java @@ -16,8 +16,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 今日头条登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.5 - * @since 1.5 + * @since 1.6.0-beta */ public class AuthToutiaoRequest extends AuthDefaultRequest { @@ -65,19 +64,21 @@ public class AuthToutiaoRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("client_key", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) - .queryParam("state", getRealState(config.getState())) .queryParam("auth_only", 1) .queryParam("display", 0) + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java b/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java index dbc029a7f37a8a4f19adb9571077ce917a0a3d97..cf87013c457376fc7d48008c319cb3e9f0bb224b 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java @@ -14,8 +14,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 微信登录 * * @author yangkai.shen (https://xkcoding.com) - * @version 1.0 - * @since 1.8 + * @since 1.1.0 */ public class AuthWeChatRequest extends AuthDefaultRequest { public AuthWeChatRequest(AuthConfig config) { @@ -100,18 +99,20 @@ public class AuthWeChatRequest extends AuthDefaultRequest { } /** - * 返回认证url,可自行跳转页面 + * 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state} * + * @param state state 验证授权流程的参数,可以防止csrf * @return 返回授权地址 + * @since 1.9.3 */ @Override - public String authorize() { + public String authorize(String state) { return UrlBuilder.fromBaseUrl(source.authorize()) .queryParam("response_type", "code") .queryParam("appid", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) .queryParam("scope", "snsapi_login") - .queryParam("state", getRealState(config.getState()).concat("#wechat_redirect")) + .queryParam("state", getRealState(state)) .build(); } diff --git a/src/main/java/me/zhyd/oauth/request/AuthWeiboRequest.java b/src/main/java/me/zhyd/oauth/request/AuthWeiboRequest.java index cf1df17d290a0902397e729209149f6ff97f7e4b..9f226d847876b189b2a9e79a4ed432c6024a7282 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthWeiboRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthWeiboRequest.java @@ -19,8 +19,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * 微博登录 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class AuthWeiboRequest extends AuthDefaultRequest { @@ -51,7 +50,7 @@ public class AuthWeiboRequest extends AuthDefaultRequest { String oauthParam = String.format("uid=%s&access_token=%s", uid, accessToken); HttpResponse response = HttpRequest.get(userInfoUrl(authToken)) .header("Authorization", "OAuth2 " + oauthParam) - .header("API-RemoteIP", IpUtils.getIp()) + .header("API-RemoteIP", IpUtils.getLocalIp()) .execute(); String userInfo = response.body(); JSONObject object = JSONObject.parseObject(userInfo); diff --git a/src/main/java/me/zhyd/oauth/utils/AuthChecker.java b/src/main/java/me/zhyd/oauth/utils/AuthChecker.java index 33b59c3556bb3686a7644e0f24746d8462517ce6..3cbd6adc0d71886fa11f35998f6f95e3dd7b5b13 100644 --- a/src/main/java/me/zhyd/oauth/utils/AuthChecker.java +++ b/src/main/java/me/zhyd/oauth/utils/AuthChecker.java @@ -9,8 +9,7 @@ import me.zhyd.oauth.model.AuthResponseStatus; * 授权配置类的校验器 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.6.1-beta */ public class AuthChecker { @@ -20,6 +19,7 @@ public class AuthChecker { * @param config config * @param source source * @return true or false + * @since 1.6.1-beta */ public static boolean isSupportedAuth(AuthConfig config, AuthSource source) { boolean isSupported = StringUtils.isNotEmpty(config.getClientId()) && StringUtils.isNotEmpty(config.getClientSecret()) && StringUtils.isNotEmpty(config.getRedirectUri()); @@ -37,6 +37,7 @@ public class AuthChecker { * * @param config config * @param source source + * @since 1.6.1-beta */ public static void checkConfig(AuthConfig config, AuthSource source) { String redirectUri = config.getRedirectUri(); @@ -57,31 +58,11 @@ public class AuthChecker { * 校验回调传回的code * * @param code 回调时传回的code + * @since 1.8.0 */ public static void checkCode(String code) { if (StringUtils.isEmpty(code)) { throw new AuthException(AuthResponseStatus.ILLEGAL_CODE); } } - - /** - * 校验state的合法性防止被CSRF - * - * @param newState 新的state,一般为回调时传回的state(可能被篡改) - * @param originalState 原始的state,发起授权时向第三方平台传递的state - */ - public static void checkState(String newState, String originalState) { - // 如果原始state为空,表示当前平台未使用state - if (StringUtils.isEmpty(originalState)) { - return; - } - // 如果授权之前使用了state,但是回调时未返回state,则表示当前请求为非法的请求,可能正在被CSRF攻击 - if (StringUtils.isEmpty(newState)) { - throw new AuthException(AuthResponseStatus.ILLEGAL_REQUEST); - } - // 如果授权前后的state不一致,则表示当前请求为非法的请求,新的state可能为伪造 - if (!newState.equals(originalState)) { - throw new AuthException(AuthResponseStatus.ILLEGAL_REQUEST); - } - } } diff --git a/src/main/java/me/zhyd/oauth/utils/AuthState.java b/src/main/java/me/zhyd/oauth/utils/AuthState.java deleted file mode 100644 index 1ca1b70f3915ffb346159e49109065e0239cbdc4..0000000000000000000000000000000000000000 --- a/src/main/java/me/zhyd/oauth/utils/AuthState.java +++ /dev/null @@ -1,230 +0,0 @@ -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.config.AuthSource; -import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.model.AuthResponseStatus; - -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(AuthSource source) { - return create(source.name()); - } - - /** - * 生成随机的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(AuthSource source, Object body) { - return create(source, JSON.toJSONString(body)); - } - - /** - * 创建state - * - * @param source oauth平台 - * @param body 希望加密到state的消息体 - * @return state - */ - public static String create(String source, Object body) { - return create(source, JSON.toJSONString(body)); - } - - /** - * 创建state - * - * @param source oauth平台 - * @param body 希望加密到state的消息体 - * @return state - */ - public static String create(AuthSource source, String body) { - return create(source.name(), body); - } - - /** - * 创建state - * - * @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(AuthSource source) { - return get(source.name()); - } - - /** - * 获取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的实际类型 - * @param 需要转换的具体的class类型 - * @return state - */ - public static T getBody(AuthSource source, String state, Class clazz) { - return getBody(source.name(), state, clazz); - } - - /** - * 获取state中保存的body内容 - * - * @param source oauth平台 - * @param state 加密后的state - * @param clazz body的实际类型 - * @param 需要转换的具体的class类型 - * @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(AuthResponseStatus.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); - } - - /** - * 登录成功后,清除state - * - * @param source oauth平台 - */ - public static void delete(AuthSource source) { - delete(source.name()); - } - - private static String getCurrentIp() { - String currentIp = IpUtils.getIp(); - return StringUtils.isEmpty(currentIp) ? EMPTY_STR : currentIp; - } -} diff --git a/src/main/java/me/zhyd/oauth/utils/AuthStateUtils.java b/src/main/java/me/zhyd/oauth/utils/AuthStateUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..4570d7b6d31c38aa9c7ad0f269e482f68e104f57 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/utils/AuthStateUtils.java @@ -0,0 +1,19 @@ +package me.zhyd.oauth.utils; + +/** + * AuthState工具类,默认只提供一个创建随机uuid的方法 + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @since 1.9.3 + */ +public class AuthStateUtils { + + /** + * 生成随机state,采用{@see https://github.com/lets-mica/mica}的UUID工具 + * + * @return 随机的state字符串 + */ + public static String createState() { + return UuidUtils.getUUID(); + } +} diff --git a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java index 88928c15f54c2a68fa84358a424ba25273bde0d6..e1aad50aa27618118a67bc8d28734828ab5db595 100644 --- a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java +++ b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java @@ -21,8 +21,7 @@ import java.util.*; * 全局的工具类 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class GlobalAuthUtil { private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8; diff --git a/src/main/java/me/zhyd/oauth/utils/IpUtils.java b/src/main/java/me/zhyd/oauth/utils/IpUtils.java index 9da2bcb561e11350c1b130ce0e25f841374b8df6..fb797cfd0b3c1b5bedfd304edeeba95258670f15 100644 --- a/src/main/java/me/zhyd/oauth/utils/IpUtils.java +++ b/src/main/java/me/zhyd/oauth/utils/IpUtils.java @@ -7,8 +7,7 @@ import java.net.UnknownHostException; * 获取IP的工具类 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.0 + * @since 1.0.0 */ public class IpUtils { @@ -17,7 +16,7 @@ public class IpUtils { * * @return ip */ - public static String getIp() { + public static String getLocalIp() { try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { @@ -25,4 +24,4 @@ public class IpUtils { return null; } } -} \ No newline at end of file +} diff --git a/src/main/java/me/zhyd/oauth/utils/StringUtils.java b/src/main/java/me/zhyd/oauth/utils/StringUtils.java index de22a435d179ee8b1010cfe44e1347763b08f784..b144bdbb699c6522c2aa12328aeafdbb20733973 100644 --- a/src/main/java/me/zhyd/oauth/utils/StringUtils.java +++ b/src/main/java/me/zhyd/oauth/utils/StringUtils.java @@ -1,9 +1,11 @@ package me.zhyd.oauth.utils; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ThreadLocalRandom; + /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 + * @since 1.0.0 */ public class StringUtils { @@ -14,4 +16,24 @@ public class StringUtils { public static boolean isNotEmpty(String str) { return !isEmpty(str); } + + /** + * 如果给定字符串{@code str}中不包含{@code appendStr},则在{@code str}后追加{@code appendStr}; + * 如果已包含{@code appendStr},则在{@code str}后追加{@code otherwise} + * + * @param str 给定的字符串 + * @param appendStr 需要追加的内容 + * @param otherwise 当{@code appendStr}不满足时追加到{@code str}后的内容 + * @return 追加后的字符串 + */ + public static String appendIfNotContain(String str, String appendStr, String otherwise) { + if (isEmpty(str) || isEmpty(appendStr)) { + return str; + } + if (str.contains(appendStr)) { + return str.concat(otherwise); + } + return str.concat(appendStr); + } + } diff --git a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java index e392f059e1225dc9fb19d6841c12b7e8966f8282..c16ea0c08a529d451dccd452adff3deb08b86600 100644 --- a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java +++ b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java @@ -14,8 +14,7 @@ import java.util.Map; *

* * @author yangkai.shen (https://xkcoding.com) - * @version 1.0 - * @since 1.8 + * @since 1.9.0 */ @Setter public class UrlBuilder { @@ -72,7 +71,7 @@ public class UrlBuilder { if (MapUtil.isEmpty(this.params)) { return this.baseUrl; } - String baseUrl = StrUtil.addSuffixIfNot(this.baseUrl, "?"); + String baseUrl = StringUtils.appendIfNotContain(this.baseUrl, "?", "&"); String paramString = GlobalAuthUtil.parseMapToString(this.params, encode); return baseUrl + paramString; } diff --git a/src/main/java/me/zhyd/oauth/utils/UuidUtils.java b/src/main/java/me/zhyd/oauth/utils/UuidUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..2782750392510de69636de82da0b0ada919ad288 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/utils/UuidUtils.java @@ -0,0 +1,65 @@ +package me.zhyd.oauth.utils; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 高性能的创建UUID的工具类,{@see https://github.com/lets-mica/mica} + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @since 1.9.3 + */ +public class UuidUtils { + + /** + * All possible chars for representing a number as a String + * copy from mica:https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/NumberUtil.java#L113 + */ + private final static byte[] DIGITS = { + '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z' + }; + + /** + * 生成uuid,采用 jdk 9 的形式,优化性能 + * copy from mica:https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L335 + *

+ * 关于mica uuid生成方式的压测结果,可以参考:https://github.com/lets-mica/mica-jmh/wiki/uuid + * + * @return UUID + */ + public static String getUUID() { + ThreadLocalRandom random = ThreadLocalRandom.current(); + long lsb = random.nextLong(); + long msb = random.nextLong(); + byte[] buf = new byte[32]; + formatUnsignedLong(lsb, buf, 20, 12); + formatUnsignedLong(lsb >>> 48, buf, 16, 4); + formatUnsignedLong(msb, buf, 12, 4); + formatUnsignedLong(msb >>> 16, buf, 8, 4); + formatUnsignedLong(msb >>> 32, buf, 0, 8); + return new String(buf, StandardCharsets.UTF_8); + } + + /** + * copy from mica:https://github.com/lets-mica/mica/blob/master/mica-core/src/main/java/net/dreamlu/mica/core/utils/StringUtil.java#L348 + */ + private static void formatUnsignedLong(long val, byte[] buf, int offset, int len) { + int charPos = offset + len; + int radix = 1 << 4; + int mask = radix - 1; + do { + buf[--charPos] = DIGITS[((int) val) & mask]; + val >>>= 4; + } while (charPos > offset); + } +} diff --git a/src/test/java/me/zhyd/oauth/AuthRequestTest.java b/src/test/java/me/zhyd/oauth/AuthRequestTest.java index a4c4a311b6b59defb484901180a1d1558136e401..9b50f536e624490b6cb5ad8814df2645a23b7a02 100644 --- a/src/test/java/me/zhyd/oauth/AuthRequestTest.java +++ b/src/test/java/me/zhyd/oauth/AuthRequestTest.java @@ -8,274 +8,292 @@ import org.junit.Test; /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 */ public class AuthRequestTest { @Test public void giteeTest() { AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state authRequest.login(new AuthCallback()); } @Test public void githubTest() { AuthRequest authRequest = new AuthGithubRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state authRequest.login(new AuthCallback()); } @Test public void weiboTest() { AuthRequest authRequest = new AuthWeiboRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state authRequest.login(new AuthCallback()); } @Test public void dingdingTest() { AuthRequest authRequest = new AuthDingTalkRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state authRequest.login(new AuthCallback()); } @Test public void baiduTest() { AuthRequest authRequest = new AuthBaiduRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state authRequest.login(new AuthCallback()); } @Test public void codingTest() { AuthRequest authRequest = new AuthCodingRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state authRequest.login(new AuthCallback()); } @Test public void tencentCloudTest() { AuthRequest authRequest = new AuthTencentCloudRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state authRequest.login(new AuthCallback()); } @Test public void oschinaTest() { AuthRequest authRequest = new AuthOschinaRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state authRequest.login(new AuthCallback()); } @Test public void alipayTest() { AuthRequest authRequest = new AuthAlipayRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .alipayPublicKey("publicKey") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .alipayPublicKey("publicKey") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void qqTest() { AuthRequest authRequest = new AuthQqRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void wechatTest() { AuthRequest authRequest = new AuthWeChatRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void taobaoTest() { AuthRequest authRequest = new AuthTaobaoRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void googleTest() { AuthRequest authRequest = new AuthGoogleRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void facebookTest() { AuthRequest authRequest = new AuthFacebookRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void douyinTest() { AuthRequest authRequest = new AuthDouyinRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void linkedinTest() { AuthRequest authRequest = new AuthLinkedinRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void microsoftTest() { AuthRequest authRequest = new AuthMicrosoftRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void miTest() { AuthRequest authRequest = new AuthMiRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } @Test public void toutiaoTest() { AuthRequest authRequest = new AuthToutiaoRequest(AuthConfig.builder() - .clientId("clientId") - .clientSecret("clientSecret") - .redirectUri("redirectUri") - .state("state") - .build()); + .clientId("clientId") + .clientSecret("clientSecret") + .redirectUri("redirectUri") + .build()); // 返回授权页面,可自行跳转 - String url = authRequest.authorize(); + authRequest.authorize("state"); // 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的入参 + // 1.9.3版本后 如果需要验证state,可以在login之前调用{@see AuthCallback#checkState}方法校验state合法性 + // 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state AuthResponse login = authRequest.login(new AuthCallback()); } } diff --git a/src/test/java/me/zhyd/oauth/cache/AuthStateCacheTest.java b/src/test/java/me/zhyd/oauth/cache/AuthStateCacheTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9c6e1e04389e60473f97fea28924f0d587dd08e1 --- /dev/null +++ b/src/test/java/me/zhyd/oauth/cache/AuthStateCacheTest.java @@ -0,0 +1,32 @@ +package me.zhyd.oauth.cache; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +public class AuthStateCacheTest { + + @Test + public void cache1() throws InterruptedException { + AuthStateCache.cache("key", "value"); + Assert.assertEquals(AuthStateCache.get("key"), "value"); + + TimeUnit.MILLISECONDS.sleep(4); + Assert.assertEquals(AuthStateCache.get("key"), "value"); + } + + @Test + public void cache2() throws InterruptedException { + AuthStateCache.cache("key", "value", 10); + Assert.assertEquals(AuthStateCache.get("key"), "value"); + + // 没过期 + TimeUnit.MILLISECONDS.sleep(5); + Assert.assertEquals(AuthStateCache.get("key"), "value"); + + // 过期 + TimeUnit.MILLISECONDS.sleep(6); + Assert.assertNull(AuthStateCache.get("key")); + } +} diff --git a/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java b/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java deleted file mode 100644 index d73489d27078c1881e0961b834a2cd640843106b..0000000000000000000000000000000000000000 --- a/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java +++ /dev/null @@ -1,231 +0,0 @@ -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 usage() { - 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/src/test/java/me/zhyd/oauth/utils/CustomTest.java b/src/test/java/me/zhyd/oauth/utils/CustomTest.java index b7a7c657725e3f73c8fe4aeba4aff1f2dae90c83..ccab7d28cff6167d4abbc5b4b45e0454631cd21d 100644 --- a/src/test/java/me/zhyd/oauth/utils/CustomTest.java +++ b/src/test/java/me/zhyd/oauth/utils/CustomTest.java @@ -1,7 +1,6 @@ package me.zhyd.oauth.utils; import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONPath; import org.junit.Test; @@ -12,11 +11,9 @@ import java.util.List; import java.util.Map; /** + * 其他测试方法 + * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @website https://www.zhyd.me - * @date 2019/7/19 15:52 - * @since 1.8 */ public class CustomTest { @@ -71,7 +68,7 @@ public class CustomTest { } @Test - public void jsonpath(){ + public void jsonpath() { List>> list = new ArrayList<>(); Map> map = new HashMap<>(); diff --git a/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java b/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java index 161031e08f1e5d4dd4f6c453a14f37366539363b..62a4239f3aaa474ecff24ac303752071c2da5336 100644 --- a/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java +++ b/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java @@ -21,18 +21,43 @@ public class UrlBuilderTest { .clientId("appid-110110110") .clientSecret("secret-110110110") .redirectUri("https://xkcoding.com") - .state(AuthState.create(AuthSource.WECHAT)) .build(); String build = UrlBuilder.fromBaseUrl(AuthSource.WECHAT.authorize()) .queryParam("appid", config.getClientId()) .queryParam("redirect_uri", config.getRedirectUri()) .queryParam("response_type", "code") .queryParam("scope", "snsapi_login") - .queryParam("state", config.getState().concat("#wechat_redirect")) + .queryParam("state", "") .build(false); + System.out.println(build); AuthWeChatRequest request = new AuthWeChatRequest(config); - String authorize = request.authorize(); - Assert.assertEquals(build, authorize); - AuthState.delete(AuthSource.WECHAT); + String authorize = request.authorize("state"); + System.out.println(authorize); + } + + @Test + public void build() { + String url = UrlBuilder.fromBaseUrl("https://www.zhyd.me") + .queryParam("name", "yadong.zhang") + .build(); + Assert.assertEquals(url, "https://www.zhyd.me?name=yadong.zhang"); + + url = UrlBuilder.fromBaseUrl(url) + .queryParam("github", "https://github.com/zhangyd-c") + .build(); + Assert.assertEquals(url, "https://www.zhyd.me?name=yadong.zhang&github=https://github.com/zhangyd-c"); + } + + @Test + public void build1() { + String url = UrlBuilder.fromBaseUrl("https://www.zhyd.me") + .queryParam("name", "yadong.zhang") + .build(true); + Assert.assertEquals(url, "https://www.zhyd.me?name=yadong.zhang"); + + url = UrlBuilder.fromBaseUrl(url) + .queryParam("github", "https://github.com/zhangyd-c") + .build(true); + Assert.assertEquals(url, "https://www.zhyd.me?name=yadong.zhang&github=https%3A%2F%2Fgithub.com%2Fzhangyd-c"); } } diff --git a/src/test/java/me/zhyd/oauth/utils/UuidUtilsTest.java b/src/test/java/me/zhyd/oauth/utils/UuidUtilsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..27b866439bda151aa2dfb726e93c3f98ee600345 --- /dev/null +++ b/src/test/java/me/zhyd/oauth/utils/UuidUtilsTest.java @@ -0,0 +1,13 @@ +package me.zhyd.oauth.utils; + +import org.junit.Test; + +public class UuidUtilsTest { + + @Test + public void getUUID() { + + String uuid = UuidUtils.getUUID(); + System.out.println(uuid); + } +} diff --git a/update.md b/update.md index 184b2c6ceac2c58c3abcb70257096ac0d517e84d..62887cbb20fdfc87f649d53bab6a7001d459bf42 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,22 @@ +### 2019/07/30 ([v1.9.3](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.3)) + +1. 规范注释 +2. 增加State缓存,`AuthCallback`中增加默认的校验state的方法 +3. 增加默认的state生成方法,参考`AuthStateUtils.java`和`UuidUtils.java` +4. 升级`hutool-http`版本到`v4.6.0` +5. 修复其他一些问题 + +### 2019/07/27 + +1. `IpUtils.getIp`改名为`IpUtils.getLocalIp` +2. 规范注释 + +### 2019/07/25 + +1. `AuthConfig`类中去掉state参数 +2. 删除`AuthState`类 +3. 增加`authorize(String)`方法,并且使用`@Deprecated`标记`authorize()`方法 + ### 2019/07/22 ([v1.9.2](https://gitee.com/yadong.zhang/JustAuth/releases/v1.9.2)) 1. 合并github上[xkcoding](https://github.com/xkcoding) 的[pr#26](https://github.com/zhangyd-c/JustAuth/pull/26),AuthConfig类添加lombok注解,方便 [justauth-spring-boot-starter](https://github.com/xkcoding/justauth-spring-boot-starter) 直接使用 @@ -34,7 +53,7 @@ 2. 将CSDN相关的类置为`Deprecated`,后续可能会删除,也可能一直保留。毕竟CSDN的openAPI已经不对外开放了。 3. `BaseAuthRequest` 改名为 `AuthDefaultRequest` 4. `ResponseStatus` 改名为 `AuthResponseStatus` 并且移动到 `me.zhyd.oauth.model` -5. 合并github上[@xkcoding](https://github.com/xkcoding) 的[pr#18](https://github.com/zhangyd-c/JustAuth/pull/18),修复小米回调错误问题 同时 支持微信获取 +5. 合并github上[@xkcoding](https://github.com/xkcoding) 的[pr#18](https://github.com/zhangyd-c/JustAuth/pull/18),修复小米回调错误问题 同时 支持微信获取unionId ### 2019/07/15 ([v1.8.1](https://gitee.com/yadong.zhang/JustAuth/releases/v1.8.1)) 1. 新增 `AuthState` 类,内置默认的state生成规则和校验规则