diff --git a/.gitignore b/.gitignore index a96470fb..e194b20f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ bin/ unpackage/ .classpath .project +*.iml .factorypath /.factorypath diff --git a/pom.xml b/pom.xml index 08ee7e92..dc0e6438 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,7 @@ sa-token-dao-redis sa-token-dao-redis-jackson sa-token-spring-aop + sa-token-oauth2 diff --git a/sa-token-core/.gitignore b/sa-token-core/.gitignore index f56feec7..8122f47c 100644 --- a/sa-token-core/.gitignore +++ b/sa-token-core/.gitignore @@ -9,4 +9,5 @@ unpackage/ .factorypath -.idea/ \ No newline at end of file +.idea/ +.iml \ No newline at end of file diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java b/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java index 3d63073a..00852a6d 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDao.java @@ -24,7 +24,7 @@ public interface SaTokenDao { * @param key 键名称 * @return value */ - public String getValue(String key); + public String get(String key); /** * 写入指定key-value键值对,并设定过期时间 (单位: 秒) @@ -32,20 +32,20 @@ public interface SaTokenDao { * @param value 值 * @param timeout 过期时间 (单位: 秒) */ - public void setValue(String key, String value, long timeout); + public void set(String key, String value, long timeout); /** - * 修改指定key-value键值对 (过期时间取原来的值) + * 修改指定key-value键值对 (过期时间不变) * @param key 键名称 * @param value 值 */ - public void updateValue(String key, String value); + public void update(String key, String value); /** * 删除一个指定的key * @param key 键名称 */ - public void deleteKey(String key); + public void delete(String key); /** * 获取指定key的剩余存活时间 (单位: 秒) @@ -62,6 +62,51 @@ public interface SaTokenDao { public void updateTimeout(String key, long timeout); + // --------------------- Object相关 --------------------- + + /** + * 根据key获取Object,如果没有,则返回空 + * @param key 键名称 + * @return object + */ + public Object getObject(String key); + + /** + * 写入指定键值对,并设定过期时间 (单位: 秒) + * @param key 键名称 + * @param object 值 + * @param timeout 过期时间 (单位: 秒) + */ + public void setObject(String key, Object object, long timeout); + + /** + * 修改指定键值对 (过期时间不变) + * @param key 键名称 + * @param object 值 + */ + public void updateObject(String key, Object object); + + /** + * 删除一个指定的Object + * @param key 键名称 + */ + public void deleteObject(String key); + + /** + * 获取指定key的剩余存活时间 (单位: 秒) + * @param key 指定key + * @return 这个key的剩余存活时间 + */ + public long getObjectTimeout(String key); + + /** + * 修改指定key的剩余存活时间 (单位: 秒) + * @param key 指定key + * @param timeout 过期时间 + */ + public void updateObjectTimeout(String key, long timeout); + + // --------------------- Session相关 --------------------- /** @@ -69,40 +114,52 @@ public interface SaTokenDao { * @param sessionId 键名称 * @return SaSession */ - public SaSession getSession(String sessionId); + public default SaSession getSession(String sessionId) { + return (SaSession)getObject(sessionId); + } /** * 将指定Session持久化 * @param session 要保存的session对象 * @param timeout 过期时间 (单位: 秒) */ - public void saveSession(SaSession session, long timeout); + public default void setSession(SaSession session, long timeout) { + setObject(session.getId(), session, timeout); + } /** * 更新指定session * @param session 要更新的session对象 */ - public void updateSession(SaSession session); + public default void updateSession(SaSession session) { + updateObject(session.getId(), session); + } /** * 删除一个指定的session * @param sessionId sessionId */ - public void deleteSession(String sessionId); + public default void deleteSession(String sessionId) { + deleteObject(sessionId); + } /** * 获取指定SaSession的剩余存活时间 (单位: 秒) * @param sessionId 指定SaSession * @return 这个SaSession的剩余存活时间 (单位: 秒) */ - public long getSessionTimeout(String sessionId); + public default long getSessionTimeout(String sessionId) { + return getObjectTimeout(sessionId); + } /** * 修改指定SaSession的剩余存活时间 (单位: 秒) * @param sessionId sessionId * @param timeout 过期时间 */ - public void updateSessionTimeout(String sessionId, long timeout); + public default void updateSessionTimeout(String sessionId, long timeout) { + updateObjectTimeout(sessionId, timeout); + } // --------------------- 会话管理 --------------------- diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java b/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java index 7ad286bd..9f438521 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/dao/SaTokenDaoDefaultImpl.java @@ -8,7 +8,6 @@ import java.util.Timer; import java.util.concurrent.ConcurrentHashMap; import cn.dev33.satoken.SaTokenManager; -import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.util.SaTaskUtil; import cn.dev33.satoken.util.SaTaskUtil.FunctionRunClass; import cn.dev33.satoken.util.SaTokenInsideUtil; @@ -42,19 +41,19 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { // ------------------------ String 读写操作 @Override - public String getValue(String key) { + public String get(String key) { clearKeyByTimeout(key); return (String)dataMap.get(key); } @Override - public void setValue(String key, String value, long timeout) { + public void set(String key, String value, long timeout) { dataMap.put(key, value); expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000)); } @Override - public void updateValue(String key, String value) { + public void update(String key, String value) { if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) { return; } @@ -62,7 +61,7 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { } @Override - public void deleteKey(String key) { + public void delete(String key) { dataMap.remove(key); expireMap.remove(key); } @@ -76,45 +75,49 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { public void updateTimeout(String key, long timeout) { expireMap.put(key, System.currentTimeMillis() + timeout * 1000); } + - - // ------------------------ Session 读写操作 + // ------------------------ Object 读写操作 @Override - public SaSession getSession(String sessionId) { - clearKeyByTimeout(sessionId); - return (SaSession)dataMap.get(sessionId); + public Object getObject(String key) { + clearKeyByTimeout(key); + return dataMap.get(key); } @Override - public void saveSession(SaSession session, long timeout) { - dataMap.put(session.getId(), session); - expireMap.put(session.getId(), (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000)); + public void setObject(String key, Object object, long timeout) { + dataMap.put(key, object); + expireMap.put(key, (timeout == SaTokenDao.NEVER_EXPIRE) ? (SaTokenDao.NEVER_EXPIRE) : (System.currentTimeMillis() + timeout * 1000)); } @Override - public void updateSession(SaSession session) { - if(getKeyTimeout(session.getId()) == SaTokenDao.NOT_VALUE_EXPIRE) { + public void updateObject(String key, Object object) { + if(getKeyTimeout(key) == SaTokenDao.NOT_VALUE_EXPIRE) { return; } // 无动作 } @Override - public void deleteSession(String sessionId) { - dataMap.remove(sessionId); - expireMap.remove(sessionId); + public void deleteObject(String key) { + dataMap.remove(key); + expireMap.remove(key); + } + + @Override + public long getObjectTimeout(String key) { + return getKeyTimeout(key); + } + + @Override + public void updateObjectTimeout(String key, long timeout) { + expireMap.put(key, System.currentTimeMillis() + timeout * 1000); } - @Override - public long getSessionTimeout(String sessionId) { - return getKeyTimeout(sessionId); - } - @Override - public void updateSessionTimeout(String sessionId, long timeout) { - expireMap.put(sessionId, System.currentTimeMillis() + timeout * 1000); - } + // ------------------------ Session 读写操作 + // 使用接口默认实现 // ------------------------ 过期时间相关操作 @@ -215,6 +218,8 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao { public List searchData(String prefix, String keyword, int start, int size) { return SaTokenInsideUtil.searchList(expireMap.keySet(), prefix, keyword, start, size); } + + diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaTokenException.java b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaTokenException.java index c7f40cfe..2c09db75 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaTokenException.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/exception/SaTokenException.java @@ -32,4 +32,14 @@ public class SaTokenException extends RuntimeException { super(cause); } + /** + * 构建一个异常 + * + * @param message 异常信息 + * @param cause 异常对象 + */ + public SaTokenException(String message, Throwable cause) { + super(message, cause); + } + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSessionCustomUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSessionCustomUtil.java index b49a3915..50567622 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSessionCustomUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/session/SaSessionCustomUtil.java @@ -46,7 +46,7 @@ public class SaSessionCustomUtil { SaSession session = SaTokenManager.getSaTokenDao().getSession(getSessionKey(sessionId)); if (session == null && isCreate) { session = SaTokenManager.getSaTokenAction().createSession(sessionId); - SaTokenManager.getSaTokenDao().saveSession(session, SaTokenManager.getConfig().getTimeout()); + SaTokenManager.getSaTokenDao().setSession(session, SaTokenManager.getConfig().getTimeout()); } return session; } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java index 43f53bc9..d1cce6ac 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpLogic.java @@ -177,7 +177,7 @@ public class StpLogic { for (TokenSign tokenSign : tokenSignList) { if(tokenSign.getDevice().equals(device)) { // 1. 将此token 标记为已顶替 - dao.updateValue(getKeyTokenValue(tokenSign.getValue()), NotLoginException.BE_REPLACED); + dao.update(getKeyTokenValue(tokenSign.getValue()), NotLoginException.BE_REPLACED); // 2. 清理掉[token-最后操作时间] clearLastActivity(tokenSign.getValue()); // 3. 清理账号session上的token签名记录 @@ -203,7 +203,7 @@ public class StpLogic { // ------ 4. 持久化其它数据 // token -> uid - dao.setValue(getKeyTokenValue(tokenValue), String.valueOf(loginId), config.getTimeout()); + dao.set(getKeyTokenValue(tokenValue), String.valueOf(loginId), config.getTimeout()); // 将token保存到本次request里 request.setAttribute(getKeyJustCreatedSave(), tokenValue); // 写入 [最后操作时间] @@ -244,7 +244,7 @@ public class StpLogic { if(loginId == null || NotLoginException.ABNORMAL_LIST.contains(loginId)) { return; } - SaTokenManager.getSaTokenDao().deleteKey(getKeyTokenValue(tokenValue)); + SaTokenManager.getSaTokenDao().delete(getKeyTokenValue(tokenValue)); // 3. 尝试清理账号session上的token签名 (如果为null或已被标记为异常, 那么无需继续执行 ) SaSession session = getSessionByLoginId(loginId, false); @@ -288,7 +288,7 @@ public class StpLogic { // 2. 清理掉[token-最后操作时间] clearLastActivity(tokenValue); // 3. 标记:已被踢下线 - SaTokenManager.getSaTokenDao().updateValue(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); + SaTokenManager.getSaTokenDao().update(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 4. 清理账号session上的token签名 session.removeTokenSign(tokenValue); } @@ -450,7 +450,7 @@ public class StpLogic { * @return loginId */ public String getLoginIdNotHandle(String tokenValue) { - return SaTokenManager.getSaTokenDao().getValue(getKeyTokenValue(tokenValue)); + return SaTokenManager.getSaTokenDao().get(getKeyTokenValue(tokenValue)); } @@ -466,7 +466,7 @@ public class StpLogic { SaSession session = SaTokenManager.getSaTokenDao().getSession(sessionId); if(session == null && isCreate) { session = SaTokenManager.getSaTokenAction().createSession(sessionId); - SaTokenManager.getSaTokenDao().saveSession(session, getConfig().getTimeout()); + SaTokenManager.getSaTokenDao().setSession(session, getConfig().getTimeout()); } return session; } @@ -589,7 +589,7 @@ public class StpLogic { return; } // 将[最后操作时间]标记为当前时间戳 - SaTokenManager.getSaTokenDao().setValue(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout()); + SaTokenManager.getSaTokenDao().set(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout()); } /** @@ -602,7 +602,7 @@ public class StpLogic { return; } // 删除[最后操作时间] - SaTokenManager.getSaTokenDao().deleteKey(getKeyLastActivityTime(tokenValue)); + SaTokenManager.getSaTokenDao().delete(getKeyLastActivityTime(tokenValue)); // 清除标记 SaTokenManager.getSaTokenServlet().getRequest().removeAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY); } @@ -654,7 +654,7 @@ public class StpLogic { if(tokenValue == null || getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) { return; } - SaTokenManager.getSaTokenDao().updateValue(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis())); + SaTokenManager.getSaTokenDao().update(getKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis())); } /** @@ -745,7 +745,7 @@ public class StpLogic { // ------ 开始查询 // 获取相关数据 String keyLastActivityTime = getKeyLastActivityTime(tokenValue); - String lastActivityTimeString = SaTokenManager.getSaTokenDao().getValue(keyLastActivityTime); + String lastActivityTimeString = SaTokenManager.getSaTokenDao().get(keyLastActivityTime); // 查不到,返回-2 if(lastActivityTimeString == null) { return SaTokenDao.NOT_VALUE_EXPIRE; diff --git a/sa-token-dao-redis-jackson/.gitignore b/sa-token-dao-redis-jackson/.gitignore index f56feec7..8122f47c 100644 --- a/sa-token-dao-redis-jackson/.gitignore +++ b/sa-token-dao-redis-jackson/.gitignore @@ -9,4 +9,5 @@ unpackage/ .factorypath -.idea/ \ No newline at end of file +.idea/ +.iml \ No newline at end of file diff --git a/sa-token-dao-redis-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedisJackson.java b/sa-token-dao-redis-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedisJackson.java index cee598ce..107f9357 100644 --- a/sa-token-dao-redis-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedisJackson.java +++ b/sa-token-dao-redis-jackson/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedisJackson.java @@ -17,7 +17,6 @@ import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.util.SaTokenInsideUtil; /** @@ -35,17 +34,17 @@ public class SaTokenDaoRedisJackson implements SaTokenDao { public ObjectMapper objectMapper; /** - * string专用 + * String专用 */ @Autowired public StringRedisTemplate stringRedisTemplate; /** - * SaSession专用 + * Object专用 */ - public RedisTemplate sessionRedisTemplate; + public RedisTemplate objectRedisTemplate; @Autowired - public void setSessionRedisTemplate(RedisConnectionFactory connectionFactory) { + public void setObjectRedisTemplate(RedisConnectionFactory connectionFactory) { // 指定相应的序列化方案 StringRedisSerializer keySerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(); @@ -60,15 +59,15 @@ public class SaTokenDaoRedisJackson implements SaTokenDao { System.err.println(e.getMessage()); } // 构建RedisTemplate - RedisTemplate template = new RedisTemplate(); + RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(connectionFactory); template.setKeySerializer(keySerializer); template.setHashKeySerializer(keySerializer); template.setValueSerializer(valueSerializer); template.setHashValueSerializer(valueSerializer); template.afterPropertiesSet(); - if(this.sessionRedisTemplate == null) { - this.sessionRedisTemplate = template; + if(this.objectRedisTemplate == null) { + this.objectRedisTemplate = template; } } @@ -77,7 +76,7 @@ public class SaTokenDaoRedisJackson implements SaTokenDao { * 根据key获取value,如果没有,则返回空 */ @Override - public String getValue(String key) { + public String get(String key) { return stringRedisTemplate.opsForValue().get(key); } @@ -85,7 +84,7 @@ public class SaTokenDaoRedisJackson implements SaTokenDao { * 写入指定key-value键值对,并设定过期时间(单位:秒) */ @Override - public void setValue(String key, String value, long timeout) { + public void set(String key, String value, long timeout) { // 判断是否为永不过期 if(timeout == SaTokenDao.NEVER_EXPIRE) { stringRedisTemplate.opsForValue().set(key, value); @@ -95,23 +94,23 @@ public class SaTokenDaoRedisJackson implements SaTokenDao { } /** - * 修改指定key-value键值对 (过期时间取原来的值) + * 修改指定key-value键值对 (过期时间不变) */ @Override - public void updateValue(String key, String value) { + public void update(String key, String value) { long expire = getTimeout(key); // -2 = 无此键 if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { return; } - this.setValue(key, value, expire); + this.set(key, value, expire); } /** * 删除一个指定的key */ @Override - public void deleteKey(String key) { + public void delete(String key) { stringRedisTemplate.delete(key); } @@ -135,7 +134,7 @@ public class SaTokenDaoRedisJackson implements SaTokenDao { // 如果其已经被设置为永久,则不作任何处理 } else { // 如果尚未被设置为永久,那么再次set一次 - this.setValue(key, this.getValue(key), timeout); + this.set(key, this.get(key), timeout); } return; } @@ -143,76 +142,77 @@ public class SaTokenDaoRedisJackson implements SaTokenDao { } - + /** - * 根据指定key的Session,如果没有,则返回空 + * 根据key获取Object,如果没有,则返回空 */ @Override - public SaSession getSession(String sessionId) { - return sessionRedisTemplate.opsForValue().get(sessionId); + public Object getObject(String key) { + return objectRedisTemplate.opsForValue().get(key); } /** - * 将指定Session持久化 + * 写入指定键值对,并设定过期时间 (单位: 秒) */ @Override - public void saveSession(SaSession session, long timeout) { + public void setObject(String key, Object object, long timeout) { // 判断是否为永不过期 if(timeout == SaTokenDao.NEVER_EXPIRE) { - sessionRedisTemplate.opsForValue().set(session.getId(), session); + objectRedisTemplate.opsForValue().set(key, object); } else { - sessionRedisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS); + objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS); } } /** - * 更新指定session + * 修改指定键值对 (过期时间不变) */ @Override - public void updateSession(SaSession session) { - long expire = getSessionTimeout(session.getId()); + public void updateObject(String key, Object object) { + long expire = getObjectTimeout(key); // -2 = 无此键 - if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { + if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { return; } - this.saveSession(session, expire); + this.setObject(key, object, expire); } /** - * 删除一个指定的session + * 删除一个指定的object */ @Override - public void deleteSession(String sessionId) { - sessionRedisTemplate.delete(sessionId); + public void deleteObject(String key) { + objectRedisTemplate.delete(key); } /** - * 获取指定SaSession的剩余存活时间 (单位: 秒) + * 获取指定key的剩余存活时间 (单位: 秒) */ @Override - public long getSessionTimeout(String sessionId) { - return sessionRedisTemplate.getExpire(sessionId); + public long getObjectTimeout(String key) { + return objectRedisTemplate.getExpire(key); } /** - * 修改指定SaSession的剩余存活时间 (单位: 秒) + * 修改指定key的剩余存活时间 (单位: 秒) */ @Override - public void updateSessionTimeout(String sessionId, long timeout) { + public void updateObjectTimeout(String key, long timeout) { // 判断是否想要设置为永久 if(timeout == SaTokenDao.NEVER_EXPIRE) { - long expire = getSessionTimeout(sessionId); + long expire = getObjectTimeout(key); if(expire == SaTokenDao.NEVER_EXPIRE) { // 如果其已经被设置为永久,则不作任何处理 } else { // 如果尚未被设置为永久,那么再次set一次 - this.saveSession(this.getSession(sessionId), timeout); + this.setObject(key, this.getObject(key), timeout); } return; } - sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS); + objectRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); } + /** * 搜索数据 */ diff --git a/sa-token-dao-redis/.gitignore b/sa-token-dao-redis/.gitignore index f56feec7..8122f47c 100644 --- a/sa-token-dao-redis/.gitignore +++ b/sa-token-dao-redis/.gitignore @@ -9,4 +9,5 @@ unpackage/ .factorypath -.idea/ \ No newline at end of file +.idea/ +.iml \ No newline at end of file diff --git a/sa-token-dao-redis/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedis.java b/sa-token-dao-redis/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedis.java index 210fc4cb..28f6413e 100644 --- a/sa-token-dao-redis/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedis.java +++ b/sa-token-dao-redis/src/main/java/cn/dev33/satoken/dao/SaTokenDaoRedis.java @@ -13,7 +13,6 @@ import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; -import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.util.SaTokenInsideUtil; /** @@ -26,30 +25,30 @@ import cn.dev33.satoken.util.SaTokenInsideUtil; public class SaTokenDaoRedis implements SaTokenDao { /** - * string专用 + * String专用 */ @Autowired public StringRedisTemplate stringRedisTemplate; /** - * SaSession专用 + * Objecy专用 */ - public RedisTemplate sessionRedisTemplate; + public RedisTemplate objectRedisTemplate; @Autowired - public void setSessionRedisTemplate(RedisConnectionFactory connectionFactory) { + public void setObjectRedisTemplate(RedisConnectionFactory connectionFactory) { // 指定相应的序列化方案 StringRedisSerializer keySerializer = new StringRedisSerializer(); JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer(); // 构建RedisTemplate - RedisTemplate template = new RedisTemplate(); + RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(connectionFactory); template.setKeySerializer(keySerializer); template.setHashKeySerializer(keySerializer); template.setValueSerializer(valueSerializer); template.setHashValueSerializer(valueSerializer); template.afterPropertiesSet(); - if(this.sessionRedisTemplate == null) { - this.sessionRedisTemplate = template; + if(this.objectRedisTemplate == null) { + this.objectRedisTemplate = template; } } @@ -58,7 +57,7 @@ public class SaTokenDaoRedis implements SaTokenDao { * 根据key获取value,如果没有,则返回空 */ @Override - public String getValue(String key) { + public String get(String key) { return stringRedisTemplate.opsForValue().get(key); } @@ -66,7 +65,7 @@ public class SaTokenDaoRedis implements SaTokenDao { * 写入指定key-value键值对,并设定过期时间(单位:秒) */ @Override - public void setValue(String key, String value, long timeout) { + public void set(String key, String value, long timeout) { // 判断是否为永不过期 if(timeout == SaTokenDao.NEVER_EXPIRE) { stringRedisTemplate.opsForValue().set(key, value); @@ -76,23 +75,23 @@ public class SaTokenDaoRedis implements SaTokenDao { } /** - * 修改指定key-value键值对 (过期时间取原来的值) + * 修改指定key-value键值对 (过期时间不变) */ @Override - public void updateValue(String key, String value) { + public void update(String key, String value) { long expire = getTimeout(key); // -2 = 无此键 if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { return; } - this.setValue(key, value, expire); + this.set(key, value, expire); } /** * 删除一个指定的key */ @Override - public void deleteKey(String key) { + public void delete(String key) { stringRedisTemplate.delete(key); } @@ -116,7 +115,7 @@ public class SaTokenDaoRedis implements SaTokenDao { // 如果其已经被设置为永久,则不作任何处理 } else { // 如果尚未被设置为永久,那么再次set一次 - this.setValue(key, this.getValue(key), timeout); + this.set(key, this.get(key), timeout); } return; } @@ -124,74 +123,73 @@ public class SaTokenDaoRedis implements SaTokenDao { } - /** - * 根据指定key的Session,如果没有,则返回空 + * 根据key获取Object,如果没有,则返回空 */ @Override - public SaSession getSession(String sessionId) { - return sessionRedisTemplate.opsForValue().get(sessionId); + public Object getObject(String key) { + return objectRedisTemplate.opsForValue().get(key); } /** - * 将指定Session持久化 + * 写入指定键值对,并设定过期时间 (单位: 秒) */ @Override - public void saveSession(SaSession session, long timeout) { + public void setObject(String key, Object object, long timeout) { // 判断是否为永不过期 if(timeout == SaTokenDao.NEVER_EXPIRE) { - sessionRedisTemplate.opsForValue().set(session.getId(), session); + objectRedisTemplate.opsForValue().set(key, object); } else { - sessionRedisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS); + objectRedisTemplate.opsForValue().set(key, object, timeout, TimeUnit.SECONDS); } } /** - * 更新指定session + * 修改指定键值对 (过期时间不变) */ @Override - public void updateSession(SaSession session) { - long expire = getSessionTimeout(session.getId()); + public void updateObject(String key, Object object) { + long expire = getObjectTimeout(key); // -2 = 无此键 if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { return; } - this.saveSession(session, expire); + this.setObject(key, object, expire); } /** - * 删除一个指定的session + * 删除一个指定的object */ @Override - public void deleteSession(String sessionId) { - sessionRedisTemplate.delete(sessionId); + public void deleteObject(String key) { + objectRedisTemplate.delete(key); } /** - * 获取指定SaSession的剩余存活时间 (单位: 秒) + * 获取指定key的剩余存活时间 (单位: 秒) */ @Override - public long getSessionTimeout(String sessionId) { - return sessionRedisTemplate.getExpire(sessionId); + public long getObjectTimeout(String key) { + return objectRedisTemplate.getExpire(key); } /** - * 修改指定SaSession的剩余存活时间 (单位: 秒) + * 修改指定key的剩余存活时间 (单位: 秒) */ @Override - public void updateSessionTimeout(String sessionId, long timeout) { + public void updateObjectTimeout(String key, long timeout) { // 判断是否想要设置为永久 if(timeout == SaTokenDao.NEVER_EXPIRE) { - long expire = getSessionTimeout(sessionId); + long expire = getObjectTimeout(key); if(expire == SaTokenDao.NEVER_EXPIRE) { // 如果其已经被设置为永久,则不作任何处理 } else { // 如果尚未被设置为永久,那么再次set一次 - this.saveSession(this.getSession(sessionId), timeout); + this.setObject(key, this.getObject(key), timeout); } return; } - sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS); + objectRedisTemplate.expire(key, timeout, TimeUnit.SECONDS); } diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-client/.gitignore b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/.gitignore new file mode 100644 index 00000000..304e8d54 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/.gitignore @@ -0,0 +1,13 @@ +target/ +.project +.classpath +.settings + +/.idea/ + +node_modules/ +bin/ +.settings/ +unpackage/ +/.apt_generated/ +/.apt_generated_tests/ diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml new file mode 100644 index 00000000..b8840778 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + com.pj + sa-token-demo-oauth2-client + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 2.3.3.RELEASE + + + + + 1.8 + 3.1.1 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + cn.dev33 + sa-token-spring-boot-starter + 1.13.0 + + + + + com.ejlchina + okhttps + 2.4.5 + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + \ No newline at end of file diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/SaOAuth2ClientApplication.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/SaOAuth2ClientApplication.java new file mode 100644 index 00000000..1a10adcf --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/SaOAuth2ClientApplication.java @@ -0,0 +1,18 @@ +package com.pj; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 启动 + * @author kong + */ +@SpringBootApplication +public class SaOAuth2ClientApplication { + + public static void main(String[] args) { + SpringApplication.run(SaOAuth2ClientApplication.class, args); + System.out.println("\n客户端启动成功,访问: http://localhost:8002/login.html"); + } + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/controller/ClientAccController.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/controller/ClientAccController.java new file mode 100644 index 00000000..489cb538 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/controller/ClientAccController.java @@ -0,0 +1,76 @@ +package com.pj.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ejlchina.okhttps.OkHttps; +import com.pj.utils.AjaxJson; +import com.pj.utils.SoMap; + +import cn.dev33.satoken.stp.StpUtil; + +/** + * 登录注册Controller + * @author kong + */ +@RestController +public class ClientAccController { + + + // 返回当前登录者的账号id, 如果未登录, 返回null + @RequestMapping("/getLoginInfo") + public AjaxJson getLoginInfo() { + Object loginId = StpUtil.getLoginIdDefaultNull(); + return AjaxJson.getSuccessData(loginId); + } + + // 注销登录 + @RequestMapping("/logout") + public AjaxJson logout() { + StpUtil.logout(); + return AjaxJson.getSuccess(); + } + + // 根据code码进行登录 + @RequestMapping("/doCodeLogin") + public AjaxJson doCodeLogin(String code) { + System.out.println("------------------ 成功进入请求 ------------------"); + + // 请求服务提供方接口地址,获取 access_token 以及其他信息 + // 携带三个关键参数: code、client_id、client_secret + String str = OkHttps.sync("http://localhost:8001/oauth2/getAccessToken") + .addBodyPara("code", code) + .addBodyPara("client_id", "123123123") + .addBodyPara("client_secret", "aaaa-bbbb-cccc-dddd-eeee") + .post() + .getBody() + .toString(); + SoMap so = SoMap.getSoMap().setJsonString(str); + System.out.println("返回结果: " + so); + + // code不等于200 代表请求失败 + if(so.getInt("code") != 200) { + return AjaxJson.getError(so.getString("msg")); + } + + // 根据openid获取其对应的userId + String openid = so.getString("openid"); + long userId = getUserIdByOpenid(openid); + + // 登录并返回账号信息 + StpUtil.setLoginId(userId); + return AjaxJson.getSuccessData(userId).set("openid", openid); + } + + + + // ------------ 模拟方法 ------------------ + + // 模拟方法:根据openid获取userId + private long getUserIdByOpenid(String openid) { + // 此方法仅做模拟,实际开发要根据具体业务逻辑来获取userId + return 10001; + } + + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/controller/ExceptionHandle.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/controller/ExceptionHandle.java new file mode 100644 index 00000000..ed687f52 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/controller/ExceptionHandle.java @@ -0,0 +1,59 @@ +package com.pj.controller; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.pj.utils.AjaxJson; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.exception.NotRoleException; + +/** + * 全局异常拦截 + *

@ControllerAdvice 可指定包前缀,例如:(basePackages = "com.pj.controller.admin") + * @author kong + * + */ +@ControllerAdvice +public class ExceptionHandle { + + + /** 全局异常拦截 */ + @ResponseBody + @ExceptionHandler + public AjaxJson handlerException(Exception e) { + + // 打印堆栈,以供调试 + e.printStackTrace(); + + // 记录日志信息 + AjaxJson aj = null; + + // ------------- 判断异常类型,提供个性化提示信息 + + // 如果是未登录异常 + if(e instanceof NotLoginException){ + aj = AjaxJson.getNotLogin(); + } + // 如果是角色异常 + else if(e instanceof NotRoleException) { + NotPermissionException ee = (NotPermissionException) e; + aj = AjaxJson.getNotJur("无此角色:" + ee.getCode()); + } + // 如果是权限异常 + else if(e instanceof NotPermissionException) { + NotPermissionException ee = (NotPermissionException) e; + aj = AjaxJson.getNotJur("无此权限:" + ee.getCode()); + } + // 普通异常输出:500 + 异常信息 + else { + aj = AjaxJson.getError(e.getMessage()); + } + + // 返回到前台 + return aj; + } + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/utils/AjaxJson.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/utils/AjaxJson.java new file mode 100644 index 00000000..5d39ac65 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/utils/AjaxJson.java @@ -0,0 +1,223 @@ +package com.pj.utils; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * ajax请求返回Json格式数据的封装
+ * 所有预留字段:
+ * code=状态码
+ * msg=描述信息
+ * data=携带对象
+ * pageNo=当前页
+ * pageSize=页大小
+ * startIndex=起始索引
+ * dataCount=数据总数
+ * pageCount=分页总数
+ *

返回范例:

+ *
+	{
+		"code": 200,    // 成功时=200, 失败时=500  msg=失败原因
+		"msg": "ok",
+		"data": {}
+	} 
+	
+ */ +public class AjaxJson extends LinkedHashMap implements Serializable{ + + private static final long serialVersionUID = 1L; // 序列化版本号 + + public static final int CODE_SUCCESS = 200; // 成功状态码 + public static final int CODE_ERROR = 500; // 错误状态码 + public static final int CODE_WARNING = 501; // 警告状态码 + public static final int CODE_NOT_JUR = 403; // 无权限状态码 + public static final int CODE_NOT_LOGIN = 401; // 未登录状态码 + public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码 + + + + // ============================ 写值取值 ================================== + + /** 给code赋值,连缀风格 */ + public AjaxJson setCode(int code) { + this.put("code", code); + return this; + } + /** 返回code */ + public Integer getCode() { + return (Integer)this.get("code"); + } + + /** 给msg赋值,连缀风格 */ + public AjaxJson setMsg(String msg) { + this.put("msg", msg); + return this; + } + /** 获取msg */ + public String getMsg() { + return (String)this.get("msg"); + } + + /** 给data赋值,连缀风格 */ + public AjaxJson setData(Object data) { + this.put("data", data); + return this; + } + /** 获取data */ + public String getData() { + return (String)this.get("data"); + } + /** 将data还原为指定类型并返回 */ + @SuppressWarnings("unchecked") + public T getData(Class cs) { + return (T) this.getData(); + } + + /** 给dataCount(数据总数)赋值,连缀风格 */ + public AjaxJson setDataCount(Long dataCount) { + this.put("dataCount", dataCount); + // 如果提供了数据总数,则尝试计算page信息 + if(dataCount != null && dataCount >= 0) { + // 如果:已有page信息 + if(get("pageNo") != null) { + this.initPageInfo(); + } +// // 或者:是JavaWeb环境 +// else if(SoMap.isJavaWeb()) { +// SoMap so = SoMap.getRequestSoMap(); +// this.setPageNoAndSize(so.getKeyPageNo(), so.getKeyPageSize()); +// this.initPageInfo(); +// } + } + return this; + } + /** 获取dataCount(数据总数) */ + public String getDataCount() { + return (String)this.get("dataCount"); + } + + /** 设置pageNo 和 pageSize,并计算出startIndex于pageCount */ + public AjaxJson setPageNoAndSize(long pageNo, long pageSize) { + this.put("pageNo", pageNo); + this.put("pageSize", pageSize); + return this; + } + + /** 根据 pageSize dataCount,计算startIndex 与 pageCount */ + public AjaxJson initPageInfo() { + long pageNo = (long)this.get("pageNo"); + long pageSize = (long)this.get("pageSize"); + long dataCount = (long)this.get("dataCount"); + this.set("startIndex", (pageNo - 1) * pageSize); + long pc = dataCount / pageSize; + this.set("pageCount", (dataCount % pageSize == 0 ? pc : pc + 1)); + return this; + } + + + /** 写入一个值 自定义key, 连缀风格 */ + public AjaxJson set(String key, Object data) { + this.put(key, data); + return this; + } + + /** 写入一个Map, 连缀风格 */ + public AjaxJson setMap(Map map) { + for (String key : map.keySet()) { + this.put(key, map.get(key)); + } + return this; + } + + + // ============================ 构建 ================================== + + public AjaxJson(int code, String msg, Object data, Long dataCount) { + this.setCode(code); + this.setMsg(msg); + this.setData(data); + this.setDataCount(dataCount == null ? -1 : dataCount); + } + + /** 返回成功 */ + public static AjaxJson getSuccess() { + return new AjaxJson(CODE_SUCCESS, "ok", null, null); + } + public static AjaxJson getSuccess(String msg) { + return new AjaxJson(CODE_SUCCESS, msg, null, null); + } + public static AjaxJson getSuccess(String msg, Object data) { + return new AjaxJson(CODE_SUCCESS, msg, data, null); + } + public static AjaxJson getSuccessData(Object data) { + return new AjaxJson(CODE_SUCCESS, "ok", data, null); + } + + + /** 返回失败 */ + public static AjaxJson getError() { + return new AjaxJson(CODE_ERROR, "error", null, null); + } + public static AjaxJson getError(String msg) { + return new AjaxJson(CODE_ERROR, msg, null, null); + } + + /** 返回警告 */ + public static AjaxJson getWarning() { + return new AjaxJson(CODE_ERROR, "warning", null, null); + } + public static AjaxJson getWarning(String msg) { + return new AjaxJson(CODE_WARNING, msg, null, null); + } + + /** 返回未登录 */ + public static AjaxJson getNotLogin() { + return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null); + } + + /** 返回没有权限的 */ + public static AjaxJson getNotJur(String msg) { + return new AjaxJson(CODE_NOT_JUR, msg, null, null); + } + + /** 返回一个自定义状态码的 */ + public static AjaxJson get(int code, String msg){ + return new AjaxJson(code, msg, null, null); + } + + /** 返回分页和数据的 */ + public static AjaxJson getPageData(Long dataCount, Object data){ + return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount); + } + + /** 返回, 根据受影响行数的(大于0=ok,小于0=error) */ + public static AjaxJson getByLine(int line){ + if(line > 0){ + return getSuccess("ok", line); + } + return getError("error").setData(line); + } + + /** 返回,根据布尔值来确定最终结果的 (true=ok,false=error) */ + public static AjaxJson getByBoolean(boolean b){ + return b ? getSuccess("ok") : getError("error"); + } + + + + + + + +// // 历史版本遗留代码 +// public int code; // 状态码 +// public String msg; // 描述信息 +// public Object data; // 携带对象 +// public Long dataCount; // 数据总数,用于分页 + + + + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/utils/SoMap.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/utils/SoMap.java new file mode 100644 index 00000000..e4524216 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/utils/SoMap.java @@ -0,0 +1,723 @@ +package com.pj.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Map< String, Object> 是最常用的一种Map类型,但是它写着麻烦 + *

所以特封装此类,继承Map,进行一些扩展,可以让Map更灵活使用 + *

最新:2020-12-10 新增部分构造方法 + * @author kong + */ +public class SoMap extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + + public SoMap() { + } + + + /** 以下元素会在isNull函数中被判定为Null, */ + public static final Object[] NULL_ELEMENT_ARRAY = {null, ""}; + public static final List NULL_ELEMENT_LIST; + + + static { + NULL_ELEMENT_LIST = Arrays.asList(NULL_ELEMENT_ARRAY); + } + + // ============================= 读值 ============================= + + /** 获取一个值 */ + @Override + public Object get(Object key) { + if("this".equals(key)) { + return this; + } + return super.get(key); + } + + /** 如果为空,则返回默认值 */ + public Object get(Object key, Object defaultValue) { + Object value = get(key); + if(valueIsNull(value)) { + return defaultValue; + } + return value; + } + + /** 转为String并返回 */ + public String getString(String key) { + Object value = get(key); + if(value == null) { + return null; + } + return String.valueOf(value); + } + + /** 如果为空,则返回默认值 */ + public String getString(String key, String defaultValue) { + Object value = get(key); + if(valueIsNull(value)) { + return defaultValue; + } + return String.valueOf(value); + } + + /** 转为int并返回 */ + public int getInt(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return 0; + } + return Integer.valueOf(String.valueOf(value)); + } + /** 转为int并返回,同时指定默认值 */ + public int getInt(String key, int defaultValue) { + Object value = get(key); + if(valueIsNull(value)) { + return defaultValue; + } + return Integer.valueOf(String.valueOf(value)); + } + + /** 转为long并返回 */ + public long getLong(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return 0; + } + return Long.valueOf(String.valueOf(value)); + } + + /** 转为double并返回 */ + public double getDouble(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return 0.0; + } + return Double.valueOf(String.valueOf(value)); + } + + /** 转为boolean并返回 */ + public boolean getBoolean(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return false; + } + return Boolean.valueOf(String.valueOf(value)); + } + + /** 转为Date并返回,根据自定义格式 */ + public Date getDateByFormat(String key, String format) { + try { + return new SimpleDateFormat(format).parse(getString(key)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** 转为Date并返回,根据格式: yyyy-MM-dd */ + public Date getDate(String key) { + return getDateByFormat(key, "yyyy-MM-dd"); + } + + /** 转为Date并返回,根据格式: yyyy-MM-dd HH:mm:ss */ + public Date getDateTime(String key) { + return getDateByFormat(key, "yyyy-MM-dd HH:mm:ss"); + } + + /** 获取集合(必须原先就是个集合,否则会创建个新集合并返回) */ + @SuppressWarnings("unchecked") + public List getList(String key) { + Object value = get(key); + List list = null; + if(value == null || value.equals("")) { + list = new ArrayList(); + } + else if(value instanceof List) { + list = (List)value; + } else { + list = new ArrayList(); + list.add(value); + } + return list; + } + + /** 获取集合 (指定泛型类型) */ + public List getList(String key, Class cs) { + List list = getList(key); + List list2 = new ArrayList(); + for (Object obj : list) { + T objC = getValueByClass(obj, cs); + list2.add(objC); + } + return list2; + } + + /** 获取集合(逗号分隔式),(指定类型) */ + public List getListByComma(String key, Class cs) { + String listStr = getString(key); + if(listStr == null || listStr.equals("")) { + return new ArrayList<>(); + } + // 开始转化 + String [] arr = listStr.split(","); + List list = new ArrayList(); + for (String str : arr) { + if(cs == int.class || cs == Integer.class || cs == long.class || cs == Long.class) { + str = str.trim(); + } + T objC = getValueByClass(str, cs); + list.add(objC); + } + return list; + } + + + /** 根据指定类型从map中取值,返回实体对象 */ + public T getModel(Class cs) { + try { + return getModelByObject(cs.newInstance()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** 从map中取值,塞到一个对象中 */ + public T getModelByObject(T obj) { + // 获取类型 + Class cs = obj.getClass(); + // 循环复制 + for (Field field : cs.getDeclaredFields()) { + try { + // 获取对象 + Object value = this.get(field.getName()); + if(value == null) { + continue; + } + field.setAccessible(true); + Object valueConvert = getValueByClass(value, field.getType()); + field.set(obj, valueConvert); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException("属性取值出错:" + field.getName(), e); + } + } + return obj; + } + + + + /** + * 将指定值转化为指定类型并返回 + * @param obj + * @param cs + * @param + * @return + */ + @SuppressWarnings("unchecked") + public static T getValueByClass(Object obj, Class cs) { + String obj2 = String.valueOf(obj); + Object obj3 = null; + if (cs.equals(String.class)) { + obj3 = obj2; + } else if (cs.equals(int.class) || cs.equals(Integer.class)) { + obj3 = Integer.valueOf(obj2); + } else if (cs.equals(long.class) || cs.equals(Long.class)) { + obj3 = Long.valueOf(obj2); + } else if (cs.equals(short.class) || cs.equals(Short.class)) { + obj3 = Short.valueOf(obj2); + } else if (cs.equals(byte.class) || cs.equals(Byte.class)) { + obj3 = Byte.valueOf(obj2); + } else if (cs.equals(float.class) || cs.equals(Float.class)) { + obj3 = Float.valueOf(obj2); + } else if (cs.equals(double.class) || cs.equals(Double.class)) { + obj3 = Double.valueOf(obj2); + } else if (cs.equals(boolean.class) || cs.equals(Boolean.class)) { + obj3 = Boolean.valueOf(obj2); + } else { + obj3 = (T)obj; + } + return (T)obj3; + } + + + // ============================= 写值 ============================= + + /** + * 给指定key添加一个默认值(只有在这个key原来无值的情况先才会set进去) + */ + public void setDefaultValue(String key, Object defaultValue) { + if(isNull(key)) { + set(key, defaultValue); + } + } + + /** set一个值,连缀风格 */ + public SoMap set(String key, Object value) { + // 防止敏感key + if(key.toLowerCase().equals("this")) { + return this; + } + put(key, value); + return this; + } + + /** 将一个Map塞进SoMap */ + public SoMap setMap(Map map) { + if(map != null) { + for (String key : map.keySet()) { + this.set(key, map.get(key)); + } + } + return this; + } + + /** 将一个对象解析塞进SoMap */ + public SoMap setModel(Object model) { + if(model == null) { + return this; + } + Field[] fields = model.getClass().getDeclaredFields(); + for (Field field : fields) { + try{ + field.setAccessible(true); + boolean isStatic = Modifier.isStatic(field.getModifiers()); + if(!isStatic) { + this.set(field.getName(), field.get(model)); + } + }catch (Exception e){ + throw new RuntimeException(e); + } + } + return this; + } + + /** 将json字符串解析后塞进SoMap */ + public SoMap setJsonString(String jsonString) { + try { + @SuppressWarnings("unchecked") + Map map = new ObjectMapper().readValue(jsonString, Map.class); + return this.setMap(map); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + + // ============================= 删值 ============================= + + /** delete一个值,连缀风格 */ + public SoMap delete(String key) { + remove(key); + return this; + } + + /** 清理所有value为null的字段 */ + public SoMap clearNull() { + Iterator iterator = this.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + if(this.isNull(key)) { + iterator.remove(); + this.remove(key); + } + + } + return this; + } + /** 清理指定key */ + public SoMap clearIn(String ...keys) { + List keys2 = Arrays.asList(keys); + Iterator iterator = this.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + if(keys2.contains(key) == true) { + iterator.remove(); + this.remove(key); + } + } + return this; + } + /** 清理掉不在列表中的key */ + public SoMap clearNotIn(String ...keys) { + List keys2 = Arrays.asList(keys); + Iterator iterator = this.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + if(keys2.contains(key) == false) { + iterator.remove(); + this.remove(key); + } + + } + return this; + } + /** 清理掉所有key */ + public SoMap clearAll() { + clear(); + return this; + } + + + // ============================= 快速构建 ============================= + + /** 构建一个SoMap并返回 */ + public static SoMap getSoMap() { + return new SoMap(); + } + /** 构建一个SoMap并返回 */ + public static SoMap getSoMap(String key, Object value) { + return new SoMap().set(key, value); + } + /** 构建一个SoMap并返回 */ + public static SoMap getSoMap(Map map) { + return new SoMap().setMap(map); + } + + /** 将一个对象集合解析成为SoMap */ + public static SoMap getSoMapByModel(Object model) { + return SoMap.getSoMap().setModel(model); + } + + /** 将一个对象集合解析成为SoMap集合 */ + public static List getSoMapByList(List list) { + List listMap = new ArrayList(); + for (Object model : list) { + listMap.add(getSoMapByModel(model)); + } + return listMap; + } + + /** 克隆指定key,返回一个新的SoMap */ + public SoMap cloneKeys(String... keys) { + SoMap so = new SoMap(); + for (String key : keys) { + so.set(key, this.get(key)); + } + return so; + } + /** 克隆所有key,返回一个新的SoMap */ + public SoMap cloneSoMap() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(key, this.get(key)); + } + return so; + } + + /** 将所有key转为大写 */ + public SoMap toUpperCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(key.toUpperCase(), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key转为小写 */ + public SoMap toLowerCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(key.toLowerCase(), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key中下划线转为中划线模式 (kebab-case风格) */ + public SoMap toKebabCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(wordEachKebabCase(key), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key中下划线转为小驼峰模式 */ + public SoMap toHumpCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(wordEachBigFs(key), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key中小驼峰转为下划线模式 */ + public SoMap humpToLineCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(wordHumpToLine(key), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + + + + + // ============================= 辅助方法 ============================= + + + /** 指定key是否为null,判定标准为 NULL_ELEMENT_ARRAY 中的元素 */ + public boolean isNull(String key) { + return valueIsNull(get(key)); + } + + /** 指定key列表中是否包含value为null的元素,只要有一个为null,就会返回true */ + public boolean isContainNull(String ...keys) { + for (String key : keys) { + if(this.isNull(key)) { + return true; + } + } + return false; + } + + /** 与isNull()相反 */ + public boolean isNotNull(String key) { + return !isNull(key); + } + /** 指定key的value是否为null,作用同isNotNull() */ + public boolean has(String key) { + return !isNull(key); + } + + /** 指定value在此SoMap的判断标准中是否为null */ + public boolean valueIsNull(Object value) { + return NULL_ELEMENT_LIST.contains(value); + } + + /** 验证指定key不为空,为空则抛出异常 */ + public SoMap checkNull(String ...keys) { + for (String key : keys) { + if(this.isNull(key)) { + throw new RuntimeException("参数" + key + "不能为空"); + } + } + return this; + } + + static Pattern patternNumber = Pattern.compile("[0-9]*"); + /** 指定key是否为数字 */ + public boolean isNumber(String key) { + String value = getString(key); + if(value == null) { + return false; + } + return patternNumber.matcher(value).matches(); + } + + + + + /** + * 转为JSON字符串 + */ + public String toJsonString() { + try { +// SoMap so = SoMap.getSoMap(this); + return new ObjectMapper().writeValueAsString(this); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +// /** +// * 转为JSON字符串, 带格式的 +// */ +// public String toJsonFormatString() { +// try { +// return JSON.toJSONString(this, true); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// } + + // ============================= web辅助 ============================= + + + /** + * 返回当前request请求的的所有参数 + * @return + */ + public static SoMap getRequestSoMap() { + // 大善人SpringMVC提供的封装 + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if(servletRequestAttributes == null) { + throw new RuntimeException("当前线程非JavaWeb环境"); + } + // 当前request + HttpServletRequest request = servletRequestAttributes.getRequest(); + if (request.getAttribute("currentSoMap") == null || request.getAttribute("currentSoMap") instanceof SoMap == false ) { + initRequestSoMap(request); + } + return (SoMap)request.getAttribute("currentSoMap"); + } + + /** 初始化当前request的 SoMap */ + private static void initRequestSoMap(HttpServletRequest request) { + SoMap soMap = new SoMap(); + Map parameterMap = request.getParameterMap(); // 获取所有参数 + for (String key : parameterMap.keySet()) { + try { + String[] values = parameterMap.get(key); // 获得values + if(values.length == 1) { + soMap.set(key, values[0]); + } else { + List list = new ArrayList(); + for (String v : values) { + list.add(v); + } + soMap.set(key, list); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + request.setAttribute("currentSoMap", soMap); + } + + /** + * 验证返回当前线程是否为JavaWeb环境 + * @return + */ + public static boolean isJavaWeb() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 大善人SpringMVC提供的封装 + if(servletRequestAttributes == null) { + return false; + } + return true; + } + + + + // ============================= 常见key (以下key经常用,所以封装以下,方便写代码) ============================= + + /** get 当前页 */ + public int getKeyPageNo() { + int pageNo = getInt("pageNo", 1); + if(pageNo <= 0) { + pageNo = 1; + } + return pageNo; + } + /** get 页大小 */ + public int getKeyPageSize() { + int pageSize = getInt("pageSize", 10); + if(pageSize <= 0 || pageSize > 1000) { + pageSize = 10; + } + return pageSize; + } + + /** get 排序方式 */ + public int getKeySortType() { + return getInt("sortType"); + } + + + + + + + // ============================= 工具方法 ============================= + + + /** + * 将一个一维集合转换为树形集合 + * @param list 集合 + * @param idKey id标识key + * @param parentIdKey 父id标识key + * @param childListKey 子节点标识key + * @return 转换后的tree集合 + */ + public static List listToTree(List list, String idKey, String parentIdKey, String childListKey) { + // 声明新的集合,存储tree形数据 + List newTreeList = new ArrayList(); + // 声明hash-Map,方便查找数据 + SoMap hash = new SoMap(); + // 将数组转为Object的形式,key为数组中的id + for (int i = 0; i < list.size(); i++) { + SoMap json = (SoMap) list.get(i); + hash.put(json.getString(idKey), json); + } + // 遍历结果集 + for (int j = 0; j < list.size(); j++) { + // 单条记录 + SoMap aVal = (SoMap) list.get(j); + // 在hash中取出key为单条记录中pid的值 + SoMap hashVp = (SoMap) hash.get(aVal.get(parentIdKey, "").toString()); + // 如果记录的pid存在,则说明它有父节点,将她添加到孩子节点的集合中 + if (hashVp != null) { + // 检查是否有child属性,有则添加,没有则新建 + if (hashVp.get(childListKey) != null) { + @SuppressWarnings("unchecked") + List ch = (List) hashVp.get(childListKey); + ch.add(aVal); + hashVp.put(childListKey, ch); + } else { + List ch = new ArrayList(); + ch.add(aVal); + hashVp.put(childListKey, ch); + } + } else { + newTreeList.add(aVal); + } + } + return newTreeList; + } + + + + /** 指定字符串的字符串下划线转大写模式 */ + private static String wordEachBig(String str){ + String newStr = ""; + for (String s : str.split("_")) { + newStr += wordFirstBig(s); + } + return newStr; + } + /** 返回下划线转小驼峰形式 */ + private static String wordEachBigFs(String str){ + return wordFirstSmall(wordEachBig(str)); + } + + /** 将指定单词首字母大写 */ + private static String wordFirstBig(String str) { + return str.substring(0, 1).toUpperCase() + str.substring(1, str.length()); + } + + /** 将指定单词首字母小写 */ + private static String wordFirstSmall(String str) { + return str.substring(0, 1).toLowerCase() + str.substring(1, str.length()); + } + + /** 下划线转中划线 */ + private static String wordEachKebabCase(String str) { + return str.replaceAll("_", "-"); + } + + /** 驼峰转下划线 */ + private static String wordHumpToLine(String str) { + return str.replaceAll("[A-Z]", "_$0").toLowerCase(); + } + + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/application.yml b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/application.yml new file mode 100644 index 00000000..5eac1b0d --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/application.yml @@ -0,0 +1,14 @@ +server: + port: 8002 + +spring: + # 静态文件路径映射 + resources: + static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/ + # static-locations: file:E:\work\project-yun\sa-token\sa-token-demo-oauth2\sa-token-demo-oauth2-client\src\main\resources\static\ + + # sa-token配置 + sa-token: + # token名称 (同时也是cookie名称) + token-name: satoken-client + \ No newline at end of file diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/static/login.html b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/static/login.html new file mode 100644 index 00000000..0287668a --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/static/login.html @@ -0,0 +1,127 @@ + + + + + 客户端-登录页 + + + + + + + + diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/.gitignore b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/.gitignore new file mode 100644 index 00000000..304e8d54 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/.gitignore @@ -0,0 +1,13 @@ +target/ +.project +.classpath +.settings + +/.idea/ + +node_modules/ +bin/ +.settings/ +unpackage/ +/.apt_generated/ +/.apt_generated_tests/ diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml new file mode 100644 index 00000000..be445b4b --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/pom.xml @@ -0,0 +1,66 @@ + + 4.0.0 + com.pj + sa-token-demo-oauth2-server + 0.0.1-SNAPSHOT + + + + org.springframework.boot + spring-boot-starter-parent + 2.3.3.RELEASE + + + + + 1.8 + 3.1.1 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + cn.dev33 + sa-token-spring-boot-starter + 1.13.0 + + + + + cn.dev33 + sa-token-oauth2 + 1.13.0-alpha + + + + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + + + + \ No newline at end of file diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java new file mode 100644 index 00000000..00eccc07 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/SaOAuth2ServerApplication.java @@ -0,0 +1,18 @@ +package com.pj; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 启动 + * @author kong + */ +@SpringBootApplication +public class SaOAuth2ServerApplication { + + public static void main(String[] args) { + SpringApplication.run(SaOAuth2ServerApplication.class, args); + System.out.println("\n服务端启动成功"); + } + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/ExceptionHandle.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/ExceptionHandle.java new file mode 100644 index 00000000..ed687f52 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/ExceptionHandle.java @@ -0,0 +1,59 @@ +package com.pj.controller; + +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.pj.utils.AjaxJson; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.exception.NotRoleException; + +/** + * 全局异常拦截 + *

@ControllerAdvice 可指定包前缀,例如:(basePackages = "com.pj.controller.admin") + * @author kong + * + */ +@ControllerAdvice +public class ExceptionHandle { + + + /** 全局异常拦截 */ + @ResponseBody + @ExceptionHandler + public AjaxJson handlerException(Exception e) { + + // 打印堆栈,以供调试 + e.printStackTrace(); + + // 记录日志信息 + AjaxJson aj = null; + + // ------------- 判断异常类型,提供个性化提示信息 + + // 如果是未登录异常 + if(e instanceof NotLoginException){ + aj = AjaxJson.getNotLogin(); + } + // 如果是角色异常 + else if(e instanceof NotRoleException) { + NotPermissionException ee = (NotPermissionException) e; + aj = AjaxJson.getNotJur("无此角色:" + ee.getCode()); + } + // 如果是权限异常 + else if(e instanceof NotPermissionException) { + NotPermissionException ee = (NotPermissionException) e; + aj = AjaxJson.getNotJur("无此权限:" + ee.getCode()); + } + // 普通异常输出:500 + 异常信息 + else { + aj = AjaxJson.getError(e.getMessage()); + } + + // 返回到前台 + return aj; + } + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/OAuth2Controller.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/OAuth2Controller.java new file mode 100644 index 00000000..9d8d6957 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/OAuth2Controller.java @@ -0,0 +1,147 @@ +package com.pj.controller; + +import java.io.IOException; +import java.net.URLDecoder; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.pj.utils.AjaxJson; +import com.pj.utils.SoMap; + +import cn.dev33.satoken.oauth2.logic.SaOAuth2Util; +import cn.dev33.satoken.oauth2.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.model.CodeModel; +import cn.dev33.satoken.oauth2.model.RequestAuthModel; +import cn.dev33.satoken.stp.StpUtil; + +@RestController +@RequestMapping("/oauth2/") +public class OAuth2Controller { + + + // 获取授权码 + @RequestMapping("/authorize") + public AjaxJson authorize(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + // 获取参数 + System.out.println("------------------ 成功进入请求 ------------------"); + + // 如果暂未登录,则先跳转到登录页 (转发) + if(StpUtil.isLogin() == false) { + response.setContentType("text/html"); + request.getRequestDispatcher("/login.html").forward(request, response); + return AjaxJson.getSuccess(); + } + + // 构建Model + RequestAuthModel authModel = new RequestAuthModel() + .setClientId(request.getParameter("client_id")) // 应用id + .setScope(request.getParameter("scope")) // 授权类型 + .setLoginId(StpUtil.getLoginIdAsLong()) // 当前登录账号id + .setRedirectUri(URLDecoder.decode(request.getParameter("redirect_uri"), "utf-8")) // 重定向地址 + .setResponseType(request.getParameter("response_type")) // 返回类型 + .setState(request.getParameter("state")) // 状态值 + .checkModel(); // 校验参数完整性 + + // 生成授权码Model + CodeModel codeModel = SaOAuth2Util.generateCode(authModel); + + // 打印调试 + System.out.println("应用id=" + authModel.getClientId() + "请求授权,授权类型=" + authModel.getResponseType()); + System.out.println("重定向地址:" + authModel.getRedirectUri()); + System.out.println("拼接完成的redirect_uri: " + codeModel.getRedirectUri()); + System.out.println("如果用户拒绝授权,则重定向至: " + codeModel.getRejectUri()); + + // 如果请求的权限用户已经确认,直接开始重定向授权 + if(codeModel.getIsConfirm() == true) { + response.sendRedirect(codeModel.getRedirectUri()); + } else { + // 如果请求的权限用户尚未确认,则进入到确定页 + request.setAttribute("name", "sdd"); + response.sendRedirect("/auth.html?code=" + codeModel.getCode()); + } + + return AjaxJson.getSuccess(); + } + + // 根据授权码获取应用信息 + @RequestMapping("/getCodeInfo") + public AjaxJson getCodeInfo(String code) { + // 获取codeModel + CodeModel codeModel = SaOAuth2Util.getCode(code); + System.out.println(code); + System.out.println(codeModel); + // 返回 + return AjaxJson.getSuccessData(codeModel); + } + + // 确认授权一个授权码 + @RequestMapping("/confirm") + public AjaxJson confirm(String code) { + // 获取codeModel + CodeModel codeModel = SaOAuth2Util.getCode(code); + if(codeModel == null) { + return AjaxJson.getError("无效code码"); + } + // 此处的判断是为了保证当前账号id 和 创建授权码的账号id一致 才可以进行确认 + if(codeModel.getLoginId().toString().equals(StpUtil.getLoginIdAsString()) == false) { + return AjaxJson.getError("暂无权限"); + } + // 进行确认 + SaOAuth2Util.confirmCode(code); + + // 返回ok + return AjaxJson.getSuccess(); + } + + // 根据授权码等参数,获取 access_token 等信息 + @RequestMapping("/getAccessToken") + public SoMap getAccessToken(HttpServletRequest request, HttpServletResponse response) throws IOException { + // 获取参数 + System.out.println("------------------ 成功进入请求 ------------------"); + String code = request.getParameter("code"); // code码 + String clientId = request.getParameter("client_id"); // 应用id + String clientSecret = request.getParameter("client_secret"); // 应用秘钥 + + // 校验参数 + SaOAuth2Util.checkCodeIdSecret(code, clientId, clientSecret); + + // 生成 + CodeModel codeModel = SaOAuth2Util.getCode(code); + AccessTokenModel tokenModel = SaOAuth2Util.generateAccessToken(codeModel); + + // 生成AccessToken之后,将授权码立即销毁 + SaOAuth2Util.deleteCode(code); + + // 返回 + return SoMap.getSoMap() + .setModel(tokenModel) + .set("code", 200) + .set("msg", "ok"); + } + + // 根据access_token返回指定的资源 + @RequestMapping("/getResources") + public SoMap getResources(HttpServletRequest request, HttpServletResponse response) throws IOException { + + // 获取信息 + String accessToken = request.getParameter("access_token"); + Object LoginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken); + System.out.println("LoginId=" + LoginId); + + // 根据LoginId获取相应信息... + // 此处仅做模拟 + return new SoMap() + .set("nickname", "shengzhang") + .set("acatar", "xxx") + .set("sex", 1); + } + + + + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/ServerAccController.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/ServerAccController.java new file mode 100644 index 00000000..1c888709 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/controller/ServerAccController.java @@ -0,0 +1,28 @@ +package com.pj.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.pj.utils.AjaxJson; + +import cn.dev33.satoken.stp.StpUtil; + +/** + * 服务端登录Controller + * @author kong + */ +@RestController +public class ServerAccController { + + // 登录方法 + @RequestMapping("/doLogin") + public AjaxJson test(String username, String password) { + System.out.println("------------------ 成功进入请求 ------------------"); + if("test".equals(username) && "test".equals(password)) { + StpUtil.setLoginId(10001); + return AjaxJson.getSuccess(); + } + return AjaxJson.getError(); + } + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2InterfaceImpl.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2InterfaceImpl.java new file mode 100644 index 00000000..708c73fa --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2InterfaceImpl.java @@ -0,0 +1,72 @@ +package com.pj.oauth2; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.stereotype.Component; + +import cn.dev33.satoken.oauth2.logic.SaOAuth2Interface; + +/** + * 使用oauth2.0 所必须的一些自定义实现 + * @author kong + */ +@Component +public class SaOAuth2InterfaceImpl implements SaOAuth2Interface { + + + /* + * ------ 注意: 以下代码均为示例,真实环境需要根据数据库查询相关信息 + */ + + // 返回此平台所有权限集合 + @Override + public List getAppScopeList() { + return Arrays.asList("userinfo"); + } + + // 返回指定Client签约的所有Scope集合 + @Override + public List getClientScopeList(String clientId) { + return Arrays.asList("userinfo"); + } + + // 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope + @Override + public List getGrantScopeList(Object loginId, String clientId) { + return Arrays.asList(); + } + + // 返回指定Client允许的回调域名, 多个用逗号隔开, *代表不限制 + @Override + public String getClientDomain(String clientId) { + return "*"; + } + + // 返回指定ClientId的ClientSecret + @Override + public String getClientSecret(String clientId) { + return "aaaa-bbbb-cccc-dddd-eeee"; + } + + // 根据ClientId和LoginId返回openid + @Override + public String getOpenid(String clientId, Object loginId) { + return "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__"; + } + + // 根据ClientId和openid返回LoginId + @Override + public Object getLoginId(String clientId, String openid) { + return 10001; + } + + + + /* + * 以上函数为开发时必须重写实现,其余函数可以按需重写 + */ + + + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2SpringAutowired.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2SpringAutowired.java new file mode 100644 index 00000000..2da17816 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2SpringAutowired.java @@ -0,0 +1,53 @@ +package com.pj.oauth2; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.logic.SaOAuth2Interface; + +/** + * 利用Spring完成自动装配 + * + * @author kong + * + */ +@Component +public class SaOAuth2SpringAutowired { + + /** + * 获取OAuth2配置Bean + * + * @return 配置对象 + */ + @Bean + @ConfigurationProperties(prefix = "spring.sa-token.oauth2") + public SaOAuth2Config getSaOAuth2Config() { + return new SaOAuth2Config(); + } + + /** + * 注入OAuth2配置Bean + * + * @param saOAuth2Config 配置对象 + */ + @Autowired + public void setSaOAuth2Config(SaOAuth2Config saOAuth2Config) { + SaOAuth2Manager.setConfig(saOAuth2Config); + } + + /** + * 注入OAuth2接口Bean + * + * @param saOAuth2Interface OAuth2接口Bean + */ + @Autowired(required = false) + public void setSaOAuth2Interface(SaOAuth2Interface saOAuth2Interface) { + SaOAuth2Manager.setInterface(saOAuth2Interface); + } + + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/utils/AjaxJson.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/utils/AjaxJson.java new file mode 100644 index 00000000..5d39ac65 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/utils/AjaxJson.java @@ -0,0 +1,223 @@ +package com.pj.utils; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * ajax请求返回Json格式数据的封装
+ * 所有预留字段:
+ * code=状态码
+ * msg=描述信息
+ * data=携带对象
+ * pageNo=当前页
+ * pageSize=页大小
+ * startIndex=起始索引
+ * dataCount=数据总数
+ * pageCount=分页总数
+ *

返回范例:

+ *
+	{
+		"code": 200,    // 成功时=200, 失败时=500  msg=失败原因
+		"msg": "ok",
+		"data": {}
+	} 
+	
+ */ +public class AjaxJson extends LinkedHashMap implements Serializable{ + + private static final long serialVersionUID = 1L; // 序列化版本号 + + public static final int CODE_SUCCESS = 200; // 成功状态码 + public static final int CODE_ERROR = 500; // 错误状态码 + public static final int CODE_WARNING = 501; // 警告状态码 + public static final int CODE_NOT_JUR = 403; // 无权限状态码 + public static final int CODE_NOT_LOGIN = 401; // 未登录状态码 + public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码 + + + + // ============================ 写值取值 ================================== + + /** 给code赋值,连缀风格 */ + public AjaxJson setCode(int code) { + this.put("code", code); + return this; + } + /** 返回code */ + public Integer getCode() { + return (Integer)this.get("code"); + } + + /** 给msg赋值,连缀风格 */ + public AjaxJson setMsg(String msg) { + this.put("msg", msg); + return this; + } + /** 获取msg */ + public String getMsg() { + return (String)this.get("msg"); + } + + /** 给data赋值,连缀风格 */ + public AjaxJson setData(Object data) { + this.put("data", data); + return this; + } + /** 获取data */ + public String getData() { + return (String)this.get("data"); + } + /** 将data还原为指定类型并返回 */ + @SuppressWarnings("unchecked") + public T getData(Class cs) { + return (T) this.getData(); + } + + /** 给dataCount(数据总数)赋值,连缀风格 */ + public AjaxJson setDataCount(Long dataCount) { + this.put("dataCount", dataCount); + // 如果提供了数据总数,则尝试计算page信息 + if(dataCount != null && dataCount >= 0) { + // 如果:已有page信息 + if(get("pageNo") != null) { + this.initPageInfo(); + } +// // 或者:是JavaWeb环境 +// else if(SoMap.isJavaWeb()) { +// SoMap so = SoMap.getRequestSoMap(); +// this.setPageNoAndSize(so.getKeyPageNo(), so.getKeyPageSize()); +// this.initPageInfo(); +// } + } + return this; + } + /** 获取dataCount(数据总数) */ + public String getDataCount() { + return (String)this.get("dataCount"); + } + + /** 设置pageNo 和 pageSize,并计算出startIndex于pageCount */ + public AjaxJson setPageNoAndSize(long pageNo, long pageSize) { + this.put("pageNo", pageNo); + this.put("pageSize", pageSize); + return this; + } + + /** 根据 pageSize dataCount,计算startIndex 与 pageCount */ + public AjaxJson initPageInfo() { + long pageNo = (long)this.get("pageNo"); + long pageSize = (long)this.get("pageSize"); + long dataCount = (long)this.get("dataCount"); + this.set("startIndex", (pageNo - 1) * pageSize); + long pc = dataCount / pageSize; + this.set("pageCount", (dataCount % pageSize == 0 ? pc : pc + 1)); + return this; + } + + + /** 写入一个值 自定义key, 连缀风格 */ + public AjaxJson set(String key, Object data) { + this.put(key, data); + return this; + } + + /** 写入一个Map, 连缀风格 */ + public AjaxJson setMap(Map map) { + for (String key : map.keySet()) { + this.put(key, map.get(key)); + } + return this; + } + + + // ============================ 构建 ================================== + + public AjaxJson(int code, String msg, Object data, Long dataCount) { + this.setCode(code); + this.setMsg(msg); + this.setData(data); + this.setDataCount(dataCount == null ? -1 : dataCount); + } + + /** 返回成功 */ + public static AjaxJson getSuccess() { + return new AjaxJson(CODE_SUCCESS, "ok", null, null); + } + public static AjaxJson getSuccess(String msg) { + return new AjaxJson(CODE_SUCCESS, msg, null, null); + } + public static AjaxJson getSuccess(String msg, Object data) { + return new AjaxJson(CODE_SUCCESS, msg, data, null); + } + public static AjaxJson getSuccessData(Object data) { + return new AjaxJson(CODE_SUCCESS, "ok", data, null); + } + + + /** 返回失败 */ + public static AjaxJson getError() { + return new AjaxJson(CODE_ERROR, "error", null, null); + } + public static AjaxJson getError(String msg) { + return new AjaxJson(CODE_ERROR, msg, null, null); + } + + /** 返回警告 */ + public static AjaxJson getWarning() { + return new AjaxJson(CODE_ERROR, "warning", null, null); + } + public static AjaxJson getWarning(String msg) { + return new AjaxJson(CODE_WARNING, msg, null, null); + } + + /** 返回未登录 */ + public static AjaxJson getNotLogin() { + return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null); + } + + /** 返回没有权限的 */ + public static AjaxJson getNotJur(String msg) { + return new AjaxJson(CODE_NOT_JUR, msg, null, null); + } + + /** 返回一个自定义状态码的 */ + public static AjaxJson get(int code, String msg){ + return new AjaxJson(code, msg, null, null); + } + + /** 返回分页和数据的 */ + public static AjaxJson getPageData(Long dataCount, Object data){ + return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount); + } + + /** 返回, 根据受影响行数的(大于0=ok,小于0=error) */ + public static AjaxJson getByLine(int line){ + if(line > 0){ + return getSuccess("ok", line); + } + return getError("error").setData(line); + } + + /** 返回,根据布尔值来确定最终结果的 (true=ok,false=error) */ + public static AjaxJson getByBoolean(boolean b){ + return b ? getSuccess("ok") : getError("error"); + } + + + + + + + +// // 历史版本遗留代码 +// public int code; // 状态码 +// public String msg; // 描述信息 +// public Object data; // 携带对象 +// public Long dataCount; // 数据总数,用于分页 + + + + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/utils/SoMap.java b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/utils/SoMap.java new file mode 100644 index 00000000..e4524216 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/utils/SoMap.java @@ -0,0 +1,723 @@ +package com.pj.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Map< String, Object> 是最常用的一种Map类型,但是它写着麻烦 + *

所以特封装此类,继承Map,进行一些扩展,可以让Map更灵活使用 + *

最新:2020-12-10 新增部分构造方法 + * @author kong + */ +public class SoMap extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + + public SoMap() { + } + + + /** 以下元素会在isNull函数中被判定为Null, */ + public static final Object[] NULL_ELEMENT_ARRAY = {null, ""}; + public static final List NULL_ELEMENT_LIST; + + + static { + NULL_ELEMENT_LIST = Arrays.asList(NULL_ELEMENT_ARRAY); + } + + // ============================= 读值 ============================= + + /** 获取一个值 */ + @Override + public Object get(Object key) { + if("this".equals(key)) { + return this; + } + return super.get(key); + } + + /** 如果为空,则返回默认值 */ + public Object get(Object key, Object defaultValue) { + Object value = get(key); + if(valueIsNull(value)) { + return defaultValue; + } + return value; + } + + /** 转为String并返回 */ + public String getString(String key) { + Object value = get(key); + if(value == null) { + return null; + } + return String.valueOf(value); + } + + /** 如果为空,则返回默认值 */ + public String getString(String key, String defaultValue) { + Object value = get(key); + if(valueIsNull(value)) { + return defaultValue; + } + return String.valueOf(value); + } + + /** 转为int并返回 */ + public int getInt(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return 0; + } + return Integer.valueOf(String.valueOf(value)); + } + /** 转为int并返回,同时指定默认值 */ + public int getInt(String key, int defaultValue) { + Object value = get(key); + if(valueIsNull(value)) { + return defaultValue; + } + return Integer.valueOf(String.valueOf(value)); + } + + /** 转为long并返回 */ + public long getLong(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return 0; + } + return Long.valueOf(String.valueOf(value)); + } + + /** 转为double并返回 */ + public double getDouble(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return 0.0; + } + return Double.valueOf(String.valueOf(value)); + } + + /** 转为boolean并返回 */ + public boolean getBoolean(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return false; + } + return Boolean.valueOf(String.valueOf(value)); + } + + /** 转为Date并返回,根据自定义格式 */ + public Date getDateByFormat(String key, String format) { + try { + return new SimpleDateFormat(format).parse(getString(key)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** 转为Date并返回,根据格式: yyyy-MM-dd */ + public Date getDate(String key) { + return getDateByFormat(key, "yyyy-MM-dd"); + } + + /** 转为Date并返回,根据格式: yyyy-MM-dd HH:mm:ss */ + public Date getDateTime(String key) { + return getDateByFormat(key, "yyyy-MM-dd HH:mm:ss"); + } + + /** 获取集合(必须原先就是个集合,否则会创建个新集合并返回) */ + @SuppressWarnings("unchecked") + public List getList(String key) { + Object value = get(key); + List list = null; + if(value == null || value.equals("")) { + list = new ArrayList(); + } + else if(value instanceof List) { + list = (List)value; + } else { + list = new ArrayList(); + list.add(value); + } + return list; + } + + /** 获取集合 (指定泛型类型) */ + public List getList(String key, Class cs) { + List list = getList(key); + List list2 = new ArrayList(); + for (Object obj : list) { + T objC = getValueByClass(obj, cs); + list2.add(objC); + } + return list2; + } + + /** 获取集合(逗号分隔式),(指定类型) */ + public List getListByComma(String key, Class cs) { + String listStr = getString(key); + if(listStr == null || listStr.equals("")) { + return new ArrayList<>(); + } + // 开始转化 + String [] arr = listStr.split(","); + List list = new ArrayList(); + for (String str : arr) { + if(cs == int.class || cs == Integer.class || cs == long.class || cs == Long.class) { + str = str.trim(); + } + T objC = getValueByClass(str, cs); + list.add(objC); + } + return list; + } + + + /** 根据指定类型从map中取值,返回实体对象 */ + public T getModel(Class cs) { + try { + return getModelByObject(cs.newInstance()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** 从map中取值,塞到一个对象中 */ + public T getModelByObject(T obj) { + // 获取类型 + Class cs = obj.getClass(); + // 循环复制 + for (Field field : cs.getDeclaredFields()) { + try { + // 获取对象 + Object value = this.get(field.getName()); + if(value == null) { + continue; + } + field.setAccessible(true); + Object valueConvert = getValueByClass(value, field.getType()); + field.set(obj, valueConvert); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException("属性取值出错:" + field.getName(), e); + } + } + return obj; + } + + + + /** + * 将指定值转化为指定类型并返回 + * @param obj + * @param cs + * @param + * @return + */ + @SuppressWarnings("unchecked") + public static T getValueByClass(Object obj, Class cs) { + String obj2 = String.valueOf(obj); + Object obj3 = null; + if (cs.equals(String.class)) { + obj3 = obj2; + } else if (cs.equals(int.class) || cs.equals(Integer.class)) { + obj3 = Integer.valueOf(obj2); + } else if (cs.equals(long.class) || cs.equals(Long.class)) { + obj3 = Long.valueOf(obj2); + } else if (cs.equals(short.class) || cs.equals(Short.class)) { + obj3 = Short.valueOf(obj2); + } else if (cs.equals(byte.class) || cs.equals(Byte.class)) { + obj3 = Byte.valueOf(obj2); + } else if (cs.equals(float.class) || cs.equals(Float.class)) { + obj3 = Float.valueOf(obj2); + } else if (cs.equals(double.class) || cs.equals(Double.class)) { + obj3 = Double.valueOf(obj2); + } else if (cs.equals(boolean.class) || cs.equals(Boolean.class)) { + obj3 = Boolean.valueOf(obj2); + } else { + obj3 = (T)obj; + } + return (T)obj3; + } + + + // ============================= 写值 ============================= + + /** + * 给指定key添加一个默认值(只有在这个key原来无值的情况先才会set进去) + */ + public void setDefaultValue(String key, Object defaultValue) { + if(isNull(key)) { + set(key, defaultValue); + } + } + + /** set一个值,连缀风格 */ + public SoMap set(String key, Object value) { + // 防止敏感key + if(key.toLowerCase().equals("this")) { + return this; + } + put(key, value); + return this; + } + + /** 将一个Map塞进SoMap */ + public SoMap setMap(Map map) { + if(map != null) { + for (String key : map.keySet()) { + this.set(key, map.get(key)); + } + } + return this; + } + + /** 将一个对象解析塞进SoMap */ + public SoMap setModel(Object model) { + if(model == null) { + return this; + } + Field[] fields = model.getClass().getDeclaredFields(); + for (Field field : fields) { + try{ + field.setAccessible(true); + boolean isStatic = Modifier.isStatic(field.getModifiers()); + if(!isStatic) { + this.set(field.getName(), field.get(model)); + } + }catch (Exception e){ + throw new RuntimeException(e); + } + } + return this; + } + + /** 将json字符串解析后塞进SoMap */ + public SoMap setJsonString(String jsonString) { + try { + @SuppressWarnings("unchecked") + Map map = new ObjectMapper().readValue(jsonString, Map.class); + return this.setMap(map); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + + // ============================= 删值 ============================= + + /** delete一个值,连缀风格 */ + public SoMap delete(String key) { + remove(key); + return this; + } + + /** 清理所有value为null的字段 */ + public SoMap clearNull() { + Iterator iterator = this.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + if(this.isNull(key)) { + iterator.remove(); + this.remove(key); + } + + } + return this; + } + /** 清理指定key */ + public SoMap clearIn(String ...keys) { + List keys2 = Arrays.asList(keys); + Iterator iterator = this.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + if(keys2.contains(key) == true) { + iterator.remove(); + this.remove(key); + } + } + return this; + } + /** 清理掉不在列表中的key */ + public SoMap clearNotIn(String ...keys) { + List keys2 = Arrays.asList(keys); + Iterator iterator = this.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + if(keys2.contains(key) == false) { + iterator.remove(); + this.remove(key); + } + + } + return this; + } + /** 清理掉所有key */ + public SoMap clearAll() { + clear(); + return this; + } + + + // ============================= 快速构建 ============================= + + /** 构建一个SoMap并返回 */ + public static SoMap getSoMap() { + return new SoMap(); + } + /** 构建一个SoMap并返回 */ + public static SoMap getSoMap(String key, Object value) { + return new SoMap().set(key, value); + } + /** 构建一个SoMap并返回 */ + public static SoMap getSoMap(Map map) { + return new SoMap().setMap(map); + } + + /** 将一个对象集合解析成为SoMap */ + public static SoMap getSoMapByModel(Object model) { + return SoMap.getSoMap().setModel(model); + } + + /** 将一个对象集合解析成为SoMap集合 */ + public static List getSoMapByList(List list) { + List listMap = new ArrayList(); + for (Object model : list) { + listMap.add(getSoMapByModel(model)); + } + return listMap; + } + + /** 克隆指定key,返回一个新的SoMap */ + public SoMap cloneKeys(String... keys) { + SoMap so = new SoMap(); + for (String key : keys) { + so.set(key, this.get(key)); + } + return so; + } + /** 克隆所有key,返回一个新的SoMap */ + public SoMap cloneSoMap() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(key, this.get(key)); + } + return so; + } + + /** 将所有key转为大写 */ + public SoMap toUpperCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(key.toUpperCase(), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key转为小写 */ + public SoMap toLowerCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(key.toLowerCase(), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key中下划线转为中划线模式 (kebab-case风格) */ + public SoMap toKebabCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(wordEachKebabCase(key), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key中下划线转为小驼峰模式 */ + public SoMap toHumpCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(wordEachBigFs(key), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key中小驼峰转为下划线模式 */ + public SoMap humpToLineCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(wordHumpToLine(key), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + + + + + // ============================= 辅助方法 ============================= + + + /** 指定key是否为null,判定标准为 NULL_ELEMENT_ARRAY 中的元素 */ + public boolean isNull(String key) { + return valueIsNull(get(key)); + } + + /** 指定key列表中是否包含value为null的元素,只要有一个为null,就会返回true */ + public boolean isContainNull(String ...keys) { + for (String key : keys) { + if(this.isNull(key)) { + return true; + } + } + return false; + } + + /** 与isNull()相反 */ + public boolean isNotNull(String key) { + return !isNull(key); + } + /** 指定key的value是否为null,作用同isNotNull() */ + public boolean has(String key) { + return !isNull(key); + } + + /** 指定value在此SoMap的判断标准中是否为null */ + public boolean valueIsNull(Object value) { + return NULL_ELEMENT_LIST.contains(value); + } + + /** 验证指定key不为空,为空则抛出异常 */ + public SoMap checkNull(String ...keys) { + for (String key : keys) { + if(this.isNull(key)) { + throw new RuntimeException("参数" + key + "不能为空"); + } + } + return this; + } + + static Pattern patternNumber = Pattern.compile("[0-9]*"); + /** 指定key是否为数字 */ + public boolean isNumber(String key) { + String value = getString(key); + if(value == null) { + return false; + } + return patternNumber.matcher(value).matches(); + } + + + + + /** + * 转为JSON字符串 + */ + public String toJsonString() { + try { +// SoMap so = SoMap.getSoMap(this); + return new ObjectMapper().writeValueAsString(this); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +// /** +// * 转为JSON字符串, 带格式的 +// */ +// public String toJsonFormatString() { +// try { +// return JSON.toJSONString(this, true); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// } + + // ============================= web辅助 ============================= + + + /** + * 返回当前request请求的的所有参数 + * @return + */ + public static SoMap getRequestSoMap() { + // 大善人SpringMVC提供的封装 + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if(servletRequestAttributes == null) { + throw new RuntimeException("当前线程非JavaWeb环境"); + } + // 当前request + HttpServletRequest request = servletRequestAttributes.getRequest(); + if (request.getAttribute("currentSoMap") == null || request.getAttribute("currentSoMap") instanceof SoMap == false ) { + initRequestSoMap(request); + } + return (SoMap)request.getAttribute("currentSoMap"); + } + + /** 初始化当前request的 SoMap */ + private static void initRequestSoMap(HttpServletRequest request) { + SoMap soMap = new SoMap(); + Map parameterMap = request.getParameterMap(); // 获取所有参数 + for (String key : parameterMap.keySet()) { + try { + String[] values = parameterMap.get(key); // 获得values + if(values.length == 1) { + soMap.set(key, values[0]); + } else { + List list = new ArrayList(); + for (String v : values) { + list.add(v); + } + soMap.set(key, list); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + request.setAttribute("currentSoMap", soMap); + } + + /** + * 验证返回当前线程是否为JavaWeb环境 + * @return + */ + public static boolean isJavaWeb() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 大善人SpringMVC提供的封装 + if(servletRequestAttributes == null) { + return false; + } + return true; + } + + + + // ============================= 常见key (以下key经常用,所以封装以下,方便写代码) ============================= + + /** get 当前页 */ + public int getKeyPageNo() { + int pageNo = getInt("pageNo", 1); + if(pageNo <= 0) { + pageNo = 1; + } + return pageNo; + } + /** get 页大小 */ + public int getKeyPageSize() { + int pageSize = getInt("pageSize", 10); + if(pageSize <= 0 || pageSize > 1000) { + pageSize = 10; + } + return pageSize; + } + + /** get 排序方式 */ + public int getKeySortType() { + return getInt("sortType"); + } + + + + + + + // ============================= 工具方法 ============================= + + + /** + * 将一个一维集合转换为树形集合 + * @param list 集合 + * @param idKey id标识key + * @param parentIdKey 父id标识key + * @param childListKey 子节点标识key + * @return 转换后的tree集合 + */ + public static List listToTree(List list, String idKey, String parentIdKey, String childListKey) { + // 声明新的集合,存储tree形数据 + List newTreeList = new ArrayList(); + // 声明hash-Map,方便查找数据 + SoMap hash = new SoMap(); + // 将数组转为Object的形式,key为数组中的id + for (int i = 0; i < list.size(); i++) { + SoMap json = (SoMap) list.get(i); + hash.put(json.getString(idKey), json); + } + // 遍历结果集 + for (int j = 0; j < list.size(); j++) { + // 单条记录 + SoMap aVal = (SoMap) list.get(j); + // 在hash中取出key为单条记录中pid的值 + SoMap hashVp = (SoMap) hash.get(aVal.get(parentIdKey, "").toString()); + // 如果记录的pid存在,则说明它有父节点,将她添加到孩子节点的集合中 + if (hashVp != null) { + // 检查是否有child属性,有则添加,没有则新建 + if (hashVp.get(childListKey) != null) { + @SuppressWarnings("unchecked") + List ch = (List) hashVp.get(childListKey); + ch.add(aVal); + hashVp.put(childListKey, ch); + } else { + List ch = new ArrayList(); + ch.add(aVal); + hashVp.put(childListKey, ch); + } + } else { + newTreeList.add(aVal); + } + } + return newTreeList; + } + + + + /** 指定字符串的字符串下划线转大写模式 */ + private static String wordEachBig(String str){ + String newStr = ""; + for (String s : str.split("_")) { + newStr += wordFirstBig(s); + } + return newStr; + } + /** 返回下划线转小驼峰形式 */ + private static String wordEachBigFs(String str){ + return wordFirstSmall(wordEachBig(str)); + } + + /** 将指定单词首字母大写 */ + private static String wordFirstBig(String str) { + return str.substring(0, 1).toUpperCase() + str.substring(1, str.length()); + } + + /** 将指定单词首字母小写 */ + private static String wordFirstSmall(String str) { + return str.substring(0, 1).toLowerCase() + str.substring(1, str.length()); + } + + /** 下划线转中划线 */ + private static String wordEachKebabCase(String str) { + return str.replaceAll("_", "-"); + } + + /** 驼峰转下划线 */ + private static String wordHumpToLine(String str) { + return str.replaceAll("[A-Z]", "_$0").toLowerCase(); + } + + +} diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml new file mode 100644 index 00000000..4424f2d3 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml @@ -0,0 +1,41 @@ +server: + port: 8001 + +spring: + # 静态文件路径映射 + resources: + static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/ + # static-locations: file:E:\work\project-yun\sa-token\sa-token-demo-oauth2\sa-token-demo-oauth2-server\src\main\resources\static\ + + # sa-token配置 + sa-token: + # token名称 (同时也是cookie名称) + token-name: satoken-server + + + # redis配置 + redis: + # Redis数据库索引(默认为0) + database: 1 + # Redis服务器地址 + host: 127.0.0.1 + # Redis服务器连接端口 + port: 6379 + # Redis服务器连接密码(默认为空) + # password: + # 连接超时时间(毫秒) + timeout: 1000ms + lettuce: + pool: + # 连接池最大连接数 + max-active: 200 + # 连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + # 连接池中的最大空闲连接 + max-idle: 10 + # 连接池中的最小空闲连接 + min-idle: 0 + + + + \ No newline at end of file diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/static/auth.html b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/static/auth.html new file mode 100644 index 00000000..c4c733c7 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/static/auth.html @@ -0,0 +1,93 @@ + + + + + 服务提供方-确认授权页 + + + + + + + + diff --git a/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/static/login.html b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/static/login.html new file mode 100644 index 00000000..d7a43c22 --- /dev/null +++ b/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/static/login.html @@ -0,0 +1,53 @@ + + + + + 服务提供方-登录页 + + + + + + + + diff --git a/sa-token-oauth2/.gitignore b/sa-token-oauth2/.gitignore new file mode 100644 index 00000000..8122f47c --- /dev/null +++ b/sa-token-oauth2/.gitignore @@ -0,0 +1,13 @@ +target/ + +node_modules/ +bin/ +.settings/ +unpackage/ +.classpath +.project + +.factorypath + +.idea/ +.iml \ No newline at end of file diff --git a/sa-token-oauth2/README.md b/sa-token-oauth2/README.md new file mode 100644 index 00000000..5659b6ed --- /dev/null +++ b/sa-token-oauth2/README.md @@ -0,0 +1,11 @@ +# sa-token-oauth2 内测版 + +sa-token-oauth2 模块是 sa-token 实现 oauth2.0 的部分,目前该模块功能完成度较低,为避免不可预知的风险,建议仅做学习测试使用 + +## 启动步骤 + +1. 启动项目 `sa-token-demo-oauth2-server`, 此为OAuth2.0的服务提供方 +2. 启动项目 `sa-token-demo-oauth2-client`, 此为OAuth2.0的客户端 +3. 根据控制台打印,访问测试地址即可:[http://localhost:8002/login.html](http://localhost:8002/login.html) + +可结合代码注释学习查看 diff --git a/sa-token-oauth2/pom.xml b/sa-token-oauth2/pom.xml new file mode 100644 index 00000000..666c7fe7 --- /dev/null +++ b/sa-token-oauth2/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + + cn.dev33 + sa-token-parent + 1.13.0 + + jar + + sa-token-dao-redis + sa-token-oauth2 + 1.13.0-alpha + sa-token realization oauth2.0 + + + + + cn.dev33 + sa-token-core + ${sa-token-version} + + + + + + diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java new file mode 100644 index 00000000..4cf80017 --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java @@ -0,0 +1,54 @@ +package cn.dev33.satoken.oauth2; + +import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.logic.SaOAuth2Interface; +import cn.dev33.satoken.oauth2.logic.SaOAuth2InterfaceDefaultImpl; + +/** + * sa-token oauth2 模块 总控类 + * + * @author kong + * + */ +public class SaOAuth2Manager { + + /** + * OAuth2 配置 Bean + */ + private static SaOAuth2Config config; + public static SaOAuth2Config getConfig() { + if (config == null) { + // 初始化默认值 + synchronized (SaOAuth2Manager.class) { + if (config == null) { + setConfig(new SaOAuth2Config()); + } + } + } + return config; + } + public static void setConfig(SaOAuth2Config config) { + SaOAuth2Manager.config = config; + } + + /** + * sa-token-oauth2 逻辑 Bean + */ + private static SaOAuth2Interface saOAuth2Interface; + public static SaOAuth2Interface getInterface() { + if (saOAuth2Interface == null) { + // 初始化默认值 + synchronized (SaOAuth2Manager.class) { + if (saOAuth2Interface == null) { + setInterface(new SaOAuth2InterfaceDefaultImpl()); + } + } + } + return saOAuth2Interface; + } + public static void setInterface(SaOAuth2Interface interfaceObj) { + SaOAuth2Manager.saOAuth2Interface = interfaceObj; + } + + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java new file mode 100644 index 00000000..b911babb --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2Config.java @@ -0,0 +1,73 @@ +package cn.dev33.satoken.oauth2.config; + +/** + * sa-token oauth2 配置类 Model + * @author kong + * + */ +public class SaOAuth2Config { + + /** + * 授权码默认保存的时间(单位秒) 默认五分钟 + */ + private long codeTimeout = 60 * 5; + + /** + * access_token默认保存的时间(单位秒) 默认两个小时 + */ + private long accessTokenTimeout = 60 * 60 * 2; + + /** + * refresh_token默认保存的时间(单位秒) 默认30 天 + */ + private long refreshTokenTimeout = 60 * 60 * 24 * 30; + + + /** + * @return codeTimeout + */ + public long getCodeTimeout() { + return codeTimeout; + } + + /** + * @param codeTimeout 要设置的 codeTimeout + */ + public void setCodeTimeout(long codeTimeout) { + this.codeTimeout = codeTimeout; + } + + /** + * @return accessTokenTimeout + */ + public long getAccessTokenTimeout() { + return accessTokenTimeout; + } + + /** + * @param accessTokenTimeout 要设置的 accessTokenTimeout + */ + public void setAccessTokenTimeout(long accessTokenTimeout) { + this.accessTokenTimeout = accessTokenTimeout; + } + + /** + * @return refreshTokenTimeout + */ + public long getRefreshTokenTimeout() { + return refreshTokenTimeout; + } + + /** + * @param refreshTokenTimeout 要设置的 refreshTokenTimeout + */ + public void setRefreshTokenTimeout(long refreshTokenTimeout) { + this.refreshTokenTimeout = refreshTokenTimeout; + } + + + + + + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Interface.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Interface.java new file mode 100644 index 00000000..11697dbb --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Interface.java @@ -0,0 +1,553 @@ +package cn.dev33.satoken.oauth2.logic; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +import cn.dev33.satoken.SaTokenManager; +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.model.CodeModel; +import cn.dev33.satoken.oauth2.model.RequestAuthModel; +import cn.dev33.satoken.oauth2.util.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.util.SaOAuth2InsideUtil; +import cn.dev33.satoken.util.SaTokenInsideUtil; + +/** + * sa-token-oauth2 模块 逻辑接口 + * @author kong + * + */ +public interface SaOAuth2Interface { + + + // ------------------- 获取数据 + + /** + * [default] 返回此平台所有权限集合 + * @return 此平台所有权限名称集合 + */ + public default List getAppScopeList() { + return Arrays.asList("userinfo"); + } + + /** + * [default] 返回指定Client签约的所有Scope名称集合 + * @param clientId 应用id + * @return Scope集合 + */ + public default List getClientScopeList(String clientId) { + // 默认返回此APP的所有权限 + return getAppScopeList(); + } + + /** + * [default] 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope + * @param clientId 应用id + * @param loginId 账号id + * @return Scope集合 + */ + public default List getGrantScopeList(Object loginId, String clientId) { + // 默认返回空集合 + return Arrays.asList(); + } + + /** + * [default] 返回指定Client允许的回调域名, 多个用逗号隔开, *代表不限制 + * @param clientId 应用id + * @return domain集合 + */ + public default String getClientDomain(String clientId) { + return "*"; + } + + /** + * [default] 返回指定ClientId的ClientSecret + * @param clientId 应用id + * @return 此应用的秘钥 + */ + public default String getClientSecret(String clientId) { + return null; + } + + /** + * [default] 根据ClientId和LoginId返回openid + * @param clientId 应用id + * @param loginId 账号id + * @return 此账号在此Client下的openid + */ + public default String getOpenid(String clientId, Object loginId) { + return null; + } + + /** + * [default] 根据ClientId和openid返回LoginId + * @param clientId 应用id + * @param openid openid + * @return LoginId + */ + public default Object getLoginId(String clientId, String openid) { + return null; + } + + + // ------------------- 数据校验 + + /** + * [default] 检查一个 Client 是否签约了指定的Scope + * @param clientId 应用id + * @param scope 权限 + */ + public default void checkContract(String clientId, String scope) { + List clientScopeList = getClientScopeList(clientId); + List scopelist = Arrays.asList(scope.split(",")); + if(clientScopeList.containsAll(scopelist) == false) { + throw new SaTokenException("请求授权范围超出或无效"); + } + } + + /** + * [default] 指定 loginId 是否对一个 Client 授权给了指定 Scope + * @param loginId 账号id + * @param clientId 应用id + * @param scope 权限 + * @return 是否已经授权 + */ + public default boolean isGrant(Object loginId, String clientId, String scope) { + List grantScopeList = getGrantScopeList(loginId, clientId); + List scopeList = convertStringToList(scope); + return grantScopeList.containsAll(scopeList); + } + + /** + * [default] 指定Client使用指定url作为回调地址,是否合法 + * @param clientId 应用id + * @param url 指定url + */ + public default void checkRightUrl(String clientId, String url) { + // 首先检测url格式 + if(SaOAuth2InsideUtil.isUrl(url) == false) { + throw new SaTokenException("url格式错误"); + } + // ---- 检测 + + // 获取此应用允许的域名列表 + String domain = getClientDomain(clientId); + // 如果是null或者空字符串, 则代表任何域名都无法通过检查 + if(domain == null || "".equals(domain)) { + throw new SaTokenException("重定向地址无效"); + } + // 如果是*符号,代表允许任何域名 + if(SaOAuth2Consts.UNLIMITED_DOMAIN.equals(domain)) { + return; + } + + // 获取域名进行比对 + try { + String host = new URL(url).getHost(); + List domainList = convertStringToList(domain); + if(domainList.contains(host) == false) { + throw new SaTokenException("重定向地址不在列表中"); + } + } catch (MalformedURLException e) { + throw new SaTokenException("url格式错误", e); + } + } + + /** + * [default] 校验code、clientId、clientSecret 三者是否正确 + * @param code 授权码 + * @param clientId 应用id + * @param clientSecret 秘钥 + * @return CodeModel对象 + */ + public default CodeModel checkCodeIdSecret(String code, String clientId, String clientSecret) { + + // 获取授权码信息 + CodeModel codeModel = getCode(code); + + // 验证code、client_id、client_secret + if(codeModel == null) { + throw new SaTokenException("无效code"); + } + + if(codeModel.getClientId().equals(clientId) == false){ + throw new SaTokenException("无效client_id"); + } + String dbClientSecret = getClientSecret(clientId); + System.out.println(dbClientSecret); + System.out.println(clientSecret); + if(dbClientSecret == null || dbClientSecret.equals(clientSecret) == false){ + throw new SaTokenException("无效client_secret"); + } + + // 返回CodeMdoel + return codeModel; + } + + /** + * [default] 校验access_token、clientId、clientSecret 三者是否正确 + * @param accessToken access_token + * @param clientId 应用id + * @param clientSecret 秘钥 + * @return AccessTokenModel对象 + */ + public default AccessTokenModel checkTokenIdSecret(String accessToken, String clientId, String clientSecret) { + + // 获取授权码信息 + AccessTokenModel tokenModel = getAccessToken(accessToken); + + // 验证code、client_id、client_secret + if(tokenModel == null) { + throw new SaTokenException("无效access_token"); + } + if(tokenModel.getClientId().equals(clientId) == false){ + throw new SaTokenException("无效client_id"); + } + String dbClientSecret = getClientSecret(clientId); + if(dbClientSecret == null || dbClientSecret.equals(clientSecret)){ + throw new SaTokenException("无效client_secret"); + } + + // 返回AccessTokenModel + return tokenModel; + } + + + // ------------------- 逻辑相关 + + // ---- 授权码 + /** + * [default] 根据参数生成一个授权码并返回 + * @param authModel 请求授权参数Model + * @return 授权码Model + */ + public default CodeModel generateCode(RequestAuthModel authModel) { + + // 获取参数 + String clientId = authModel.getClientId(); + String scope = authModel.getScope(); + Object loginId = authModel.getLoginId(); + String redirectUri = authModel.getRedirectUri(); + String state = authModel.getState(); + + // ------ 参数校验 + // 此Client是否签约了此Scope + checkContract(clientId, scope); + + // 校验重定向域名的格式是否合法 + checkRightUrl(clientId, redirectUri); + + // ------ 开始生成code码 + String code = createCode(clientId, scope, loginId); + CodeModel codeModel = new CodeModel(code, clientId, scope, loginId); + + // 拼接授权后重定向的域名 (拼接code和state参数) + String url = splicingParame(redirectUri, "code=" + code); + if(state != null) { + url = splicingParame(url, "state=" + state); + } + codeModel.setRedirectUri(url); + + // 拒绝授权时重定向的地址 + codeModel.setRejectUri(splicingParame(redirectUri, "handle=reject")); + + // 计算此Scope是否已经授权过了 + codeModel.setIsConfirm(isGrant(loginId, clientId, scope)); + + // ------ 开始保存 + + // 将此授权码保存到DB + long codeTimeout = SaOAuth2Manager.getConfig().getCodeTimeout(); + SaTokenManager.getSaTokenDao().setObject(getKeyCodeModel(code), codeModel, codeTimeout); + + // 如果此[Client&账号]已经有code正在存储,则先删除它 + String key = getKeyClientLoginId(loginId, clientId); + SaTokenManager.getSaTokenDao().delete(key); + + // 将此[Client&账号]的最新授权码保存到DB中 + // 以便于完成授权码覆盖操作: 保证每次只有最新的授权码有效 + SaTokenManager.getSaTokenDao().set(key, code, codeTimeout); + + // 返回 + return codeModel; + } + + /** + * [default] 根据授权码获得授权码Model + * @param code 授权码 + * @return 授权码Model + */ + public default CodeModel getCode(String code) { + return (CodeModel)SaTokenManager.getSaTokenDao().getObject(getKeyCodeModel(code)); + } + + /** + * [default] 手动更改授权码对象信息 + * @param code 授权码 + * @param codeModel 授权码Model + */ + public default void updateCode(String code, CodeModel codeModel) { + SaTokenManager.getSaTokenDao().updateObject(getKeyCodeModel(code), codeModel); + } + + /** + * [default] 确认授权一个code + * @param code 授权码 + */ + public default void confirmCode(String code) { + // 获取codeModel + CodeModel codeModel = getCode(code); + // 如果该code码已经确认 + if(codeModel.getIsConfirm() == true) { + return; + } + // 进行确认 + codeModel.setIsConfirm(true); + updateCode(code, codeModel); + } + + /** + * [default] 删除一个授权码 + * @param code 授权码 + */ + public default void deleteCode(String code) { + SaTokenManager.getSaTokenDao().deleteObject(getKeyCodeModel(code)); + } + + + // ------------------- access_token 和 refresh_token 相关 + + /** + * [default] 根据授权码Model生成一个access_token + * @param codeModel 授权码Model + * @return AccessTokenModel + */ + public default AccessTokenModel generateAccessToken(CodeModel codeModel) { + + // 先校验 + if(codeModel == null) { + throw new SaTokenException("无效code"); + } + if(codeModel.getIsConfirm() == false) { + throw new SaTokenException("该code尚未授权"); + } + + // 获取 TokenModel 并保存 + AccessTokenModel tokenModel = converCodeToAccessToken(codeModel); + SaTokenManager.getSaTokenDao().setObject(getKeyAccessToken(tokenModel.getAccessToken()), tokenModel, SaOAuth2Manager.getConfig().getAccessTokenTimeout()); + + // 将此 CodeModel 当做 refresh_token 保存下来 + SaTokenManager.getSaTokenDao().setObject(getKeyRefreshToken(tokenModel.getRefreshToken()), codeModel, SaOAuth2Manager.getConfig().getRefreshTokenTimeout()); + + // 返回 + return tokenModel; + } + + /** + * [default] 根据 access_token 获得其Model详细信息 + * @param accessToken access_token + * @return AccessTokenModel (授权码Model) + */ + public default AccessTokenModel getAccessToken(String accessToken) { + return (AccessTokenModel)SaTokenManager.getSaTokenDao().getObject(getKeyAccessToken(accessToken)); + } + + /** + * [default] 根据 refresh_token 生成一个新的 access_token + * @param refreshToken refresh_token + * @return 新的 access_token + */ + public default AccessTokenModel refreshAccessToken(String refreshToken) { + // 获取Model信息 + CodeModel codeModel = getRefreshToken(refreshToken); + if(codeModel == null) { + throw new SaTokenException("无效refresh_token"); + } + // 获取新的 AccessToken 并保存 + AccessTokenModel tokenModel = converCodeToAccessToken(codeModel); + SaTokenManager.getSaTokenDao().setObject(getKeyAccessToken(tokenModel.getAccessToken()), tokenModel, SaOAuth2Manager.getConfig().getAccessTokenTimeout()); + + // 返回 + return tokenModel; + } + + /** + * [default] 根据 refresh_token 获得其Model详细信息 (授权码Model) + * @param refreshToken refresh_token + * @return RefreshToken (授权码Model) + */ + public default CodeModel getRefreshToken(String refreshToken) { + return (CodeModel)SaTokenManager.getSaTokenDao().getObject(getKeyRefreshToken(refreshToken)); + } + + /** + * [default] 获取 access_token 的有效期 + * @param accessToken access_token + * @return 有效期 + */ + public default long getAccessTokenExpiresIn(String accessToken) { + return SaTokenManager.getSaTokenDao().getObjectTimeout(getKeyAccessToken(accessToken)); + } + + /** + * [default] 获取 refresh_token 的有效期 + * @param refreshToken refresh_token + * @return 有效期 + */ + public default long getRefreshTokenExpiresIn(String refreshToken) { + return SaTokenManager.getSaTokenDao().getObjectTimeout(getKeyRefreshToken(refreshToken)); + } + + /** + * [default] 获取 access_token 所代表的LoginId + * @param accessToken access_token + * @return LoginId + */ + public default Object getLoginIdByAccessToken(String accessToken) { + AccessTokenModel tokenModel = SaOAuth2Util.getAccessToken(accessToken); + if(tokenModel == null) { + throw new SaTokenException("无效access_token"); + } + return getLoginId(tokenModel.getClientId(), tokenModel.getOpenid()); + } + + + // ------------------- 自定义策略相关 + + /** + * [default] 将指定字符串按照逗号分隔符转化为字符串集合 + * @param str 字符串 + * @return 分割后的字符串集合 + */ + public default List convertStringToList(String str) { + return Arrays.asList(str.split(",")); + } + + /** + * [default] 生成授权码 + * @param clientId 应用id + * @param scope 权限 + * @param loginId 账号id + * @return 授权码 + */ + public default String createCode(String clientId, String scope, Object loginId) { + return SaTokenInsideUtil.getRandomString(60).toLowerCase(); + } + + /** + * [default] 生成AccessToken + * @param codeModel CodeModel对象 + * @return AccessToken + */ + public default String createAccessToken(CodeModel codeModel) { + return SaTokenInsideUtil.getRandomString(60).toLowerCase(); + } + + /** + * [default] 生成RefreshToken + * @param codeModel CodeModel对象 + * @return RefreshToken + */ + public default String createRefreshToken(CodeModel codeModel) { + return SaTokenInsideUtil.getRandomString(60).toLowerCase(); + } + + /** + * [default] 在url上拼接上kv参数并返回 + * @param url url + * @param parameStr 参数, 例如 id=1001 + * @return 拼接后的url字符串 + */ + public default String splicingParame(String url, String parameStr) { + // 如果参数为空, 直接返回 + if(parameStr == null || parameStr.length() == 0) { + return url; + } + int index = url.indexOf('?'); + // ? 不存在 + if(index == -1) { + return url + '?' + parameStr; + } + // ? 是最后一位 + if(index == url.length() - 1) { + return url + parameStr; + } + // ? 是其中一位 + if(index > -1 && index < url.length() - 1) { + String separatorChar = "&"; + // 如果最后一位是 不是&, 且 arg_str 第一位不是 &, 就增送一个 & + if(url.lastIndexOf(separatorChar) != url.length() - 1 && parameStr.indexOf(separatorChar) != 0) { + return url + separatorChar + parameStr; + } else { + return url + parameStr; + } + } + // 正常情况下, 代码不可能执行到此 + return url; + } + + /** + * [default] 将 CodeModel 转换为 AccessTokenModel + * @param codeModel CodeModel对象 + * @return AccessToken对象 + */ + public default AccessTokenModel converCodeToAccessToken(CodeModel codeModel) { + if(codeModel == null) { + throw new SaTokenException("无效code"); + } + AccessTokenModel tokenModel = new AccessTokenModel(); + tokenModel.setAccessToken(createAccessToken(codeModel)); + tokenModel.setRefreshToken(createRefreshToken(codeModel)); + tokenModel.setCode(codeModel.getCode()); + tokenModel.setClientId(codeModel.getClientId()); + tokenModel.setScope(codeModel.getScope()); + tokenModel.setOpenid(getOpenid(codeModel.getClientId(), codeModel.getLoginId())); + tokenModel.setTag(codeModel.getTag()); + return tokenModel; + } + + + // ------------------- 返回相应key + + /** + * 获取key:授权码持久化使用的key + * @param code 授权码 + * @return key + */ + public default String getKeyCodeModel(String code) { + return SaTokenManager.getConfig().getTokenName() + ":oauth2:code:" + code; + } + + /** + * 获取key:[Client&账号]最新授权码记录, 持久化使用的key + * @param loginId 账号id + * @param clientId 应用id + * @return key + */ + public default String getKeyClientLoginId(Object loginId, String clientId) { + return SaTokenManager.getConfig().getTokenName() + ":oauth2:newest-code:" + clientId + ":" + loginId; + } + + /** + * 获取key:refreshToken持久化使用的key + * @param refreshToken refreshToken + * @return key + */ + public default String getKeyRefreshToken(String refreshToken) { + return SaTokenManager.getConfig().getTokenName() + ":oauth2:refresh-token:" + refreshToken; + } + + /** + * 获取key:accessToken持久化使用的key + * @param accessToken accessToken + * @return key + */ + public default String getKeyAccessToken(String accessToken) { + return SaTokenManager.getConfig().getTokenName() + ":oauth2:access-token:" + accessToken; + } + + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2InterfaceDefaultImpl.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2InterfaceDefaultImpl.java new file mode 100644 index 00000000..20d23790 --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2InterfaceDefaultImpl.java @@ -0,0 +1,12 @@ +package cn.dev33.satoken.oauth2.logic; + +/** + * SaOAuth2Interface 默认实现类 (只构建userinfo单个权限) + * @author kong + * + */ +public class SaOAuth2InterfaceDefaultImpl implements SaOAuth2Interface { + + + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java new file mode 100644 index 00000000..2c5ecbcc --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java @@ -0,0 +1,197 @@ +package cn.dev33.satoken.oauth2.logic; + +import java.util.List; + +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.model.AccessTokenModel; +import cn.dev33.satoken.oauth2.model.CodeModel; +import cn.dev33.satoken.oauth2.model.RequestAuthModel; + +/** + * sa-token-oauth2 模块 静态类接口转发, 方便调用 + * @author kong + * + */ +public class SaOAuth2Util { + + // ------------------- 获取数据 + + /** + * 返回此平台所有权限集合 + * @return 此平台所有权限名称集合 + */ + public static List getAppScopeList() { + return SaOAuth2Manager.getInterface().getAppScopeList(); + } + + /** + * 返回指定Client签约的所有Scope名称集合 + * @param clientId 应用id + * @return Scope集合 + */ + public static List getClientScopeList(String clientId) { + return SaOAuth2Manager.getInterface().getClientScopeList(clientId); + } + + /** + * 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope + * @param clientId 应用id + * @param loginId 账号id + * @return Scope集合 + */ + public static List getGrantScopeList(Object loginId, String clientId) { + return SaOAuth2Manager.getInterface().getGrantScopeList(loginId, clientId); + } + + + // ------------------- 数据校验 + + /** + * 指定 loginId 是否对一个 Client 授权给了指定 Scope + * @param clientId 应用id + * @param scope 权限 + * @param loginId 账号id + */ + public static boolean isGrant(Object loginId, String clientId, String scope) { + return SaOAuth2Manager.getInterface().isGrant(loginId, clientId, scope); + } + + /** + * 校验code、clientId、clientSecret 三者是否正确 + * @param code 授权码 + * @param clientId 应用id + * @param clientSecret 秘钥 + * @return CodeModel对象 + */ + public static CodeModel checkCodeIdSecret(String code, String clientId, String clientSecret) { + return SaOAuth2Manager.getInterface().checkCodeIdSecret(code, clientId, clientSecret); + } + + /** + * [default] 校验access_token、clientId、clientSecret 三者是否正确 + * @param accessToken access_token + * @param clientId 应用id + * @param clientSecret 秘钥 + * @return AccessTokenModel对象 + */ + public static AccessTokenModel checkTokenIdSecret(String accessToken, String clientId, String clientSecret) { + return SaOAuth2Manager.getInterface().checkTokenIdSecret(accessToken, clientId, clientSecret); + } + + + + // ------------------- 逻辑相关 + + /** + * 根据参数生成一个授权码并返回 + * @param authModel 请求授权参数Model + * @return 授权码Model + */ + public static CodeModel generateCode(RequestAuthModel authModel) { + return SaOAuth2Manager.getInterface().generateCode(authModel); + } + + /** + * 根据授权码获得授权码Model + * @param code 授权码 + * @return 授权码Model + */ + public static CodeModel getCode(String code) { + return SaOAuth2Manager.getInterface().getCode(code); + } + + /** + * 手动更改授权码对象信息 + * @param code 授权码 + * @param codeModel 授权码Model + */ + public static void updateCode(String code, CodeModel codeModel) { + SaOAuth2Manager.getInterface().updateCode(code, codeModel); + } + + /** + * 确认授权一个code + * @param code 授权码 + */ + public static void confirmCode(String code) { + SaOAuth2Manager.getInterface().confirmCode(code); + } + + /** + * [default] 删除一个授权码 + * @param code 授权码 + */ + public static void deleteCode(String code) { + SaOAuth2Manager.getInterface().deleteCode(code); + } + + /** + * [default] 根据授权码Model生成一个access_token + * @param codeModel 授权码Model + * @return AccessTokenModel + */ + public static AccessTokenModel generateAccessToken(CodeModel codeModel) { + return SaOAuth2Manager.getInterface().generateAccessToken(codeModel); + } + + /** + * [default] 根据 access_token 获得其Model详细信息 + * @param accessToken access_token + * @return AccessTokenModel (授权码Model) + */ + public static AccessTokenModel getAccessToken(String accessToken) { + return SaOAuth2Manager.getInterface().getAccessToken(accessToken); + } + + /** + * 根据 refresh_token 生成一个新的 access_token + * @param refreshToken refresh_token + * @return 新的 access_token + */ + public static AccessTokenModel refreshAccessToken(String refreshToken) { + return SaOAuth2Manager.getInterface().refreshAccessToken(refreshToken); + } + + /** + * [default] 根据 refresh_token 获得其Model详细信息 (授权码Model) + * @param refreshToken refresh_token + * @return RefreshToken (授权码Model) + */ + public static CodeModel getRefreshToken(String refreshToken) { + return SaOAuth2Manager.getInterface().getRefreshToken(refreshToken); + } + + /** + * [default] 获取 access_token 的有效期 + * @param accessToken access_token + * @return 有效期 + */ + public static long getAccessTokenExpiresIn(String accessToken) { + return SaOAuth2Manager.getInterface().getAccessTokenExpiresIn(accessToken); + } + + /** + * [default] 获取 refresh_token 的有效期 + * @param refreshToken refresh_token + * @return 有效期 + */ + public static long getRefreshTokenExpiresIn(String refreshToken) { + return SaOAuth2Manager.getInterface().getRefreshTokenExpiresIn(refreshToken); + } + + /** + * [default] 获取 access_token 所代表的LoginId + * @param accessToken access_token + * @return LoginId + */ + public static Object getLoginIdByAccessToken(String accessToken) { + return SaOAuth2Manager.getInterface().getLoginIdByAccessToken(accessToken); + } + + + + + + + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/AccessTokenModel.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/AccessTokenModel.java new file mode 100644 index 00000000..21a36fa9 --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/AccessTokenModel.java @@ -0,0 +1,186 @@ +package cn.dev33.satoken.oauth2.model; + +/** + * Model: access_token + * @author kong + * + */ +public class AccessTokenModel { + + /** + * access_token 值 + */ + private String accessToken; + + /** + * refresh_token 值 + */ + private String refreshToken; + + /** + * access_token 剩余有效时间 (秒) + */ + private long expiresIn; + + /** + * refresh_token 剩余有效期 (秒) + */ + private long refreshExpiresIn; + + /** + * 此 access_token令牌 是由哪个code码创建 + */ + private String code; + + /** + * 应用id + */ + private String clientId; + + /** + * 授权范围 + */ + private String scope; + + /** + * 开放账号id + */ + private String openid; + + /** + * 其他自定义数据 + */ + private Object tag; + + + /** + * @return accessToken + */ + public String getAccessToken() { + return accessToken; + } + + /** + * @param accessToken 要设置的 accessToken + */ + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + /** + * @return refreshToken + */ + public String getRefreshToken() { + return refreshToken; + } + + /** + * @param refreshToken 要设置的 refreshToken + */ + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + /** + * @return expiresIn + */ + public long getExpiresIn() { + return expiresIn; + } + + /** + * @param expiresIn 要设置的 expiresIn + */ + public void setExpiresIn(long expiresIn) { + this.expiresIn = expiresIn; + } + + /** + * @return refreshExpiresIn + */ + public long getRefreshExpiresIn() { + return refreshExpiresIn; + } + + /** + * @param refreshExpiresIn 要设置的 refreshExpiresIn + */ + public void setRefreshExpiresIn(long refreshExpiresIn) { + this.refreshExpiresIn = refreshExpiresIn; + } + + /** + * @return code + */ + public String getCode() { + return code; + } + + /** + * @param code 要设置的 code + */ + public void setCode(String code) { + this.code = code; + } + + /** + * @return clientId + */ + public String getClientId() { + return clientId; + } + + /** + * @param clientId 要设置的 clientId + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * @return scope + */ + public String getScope() { + return scope; + } + + /** + * @param scope 要设置的 scope + */ + public void setScope(String scope) { + this.scope = scope; + } + + /** + * @return openid + */ + public String getOpenid() { + return openid; + } + + /** + * @param openid 要设置的 openid + */ + public void setOpenid(String openid) { + this.openid = openid; + } + + /** + * @return tag + */ + public Object getTag() { + return tag; + } + + /** + * @param tag 要设置的 tag + */ + public void setTag(Object tag) { + this.tag = tag; + } + + + + + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/CodeModel.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/CodeModel.java new file mode 100644 index 00000000..5600606d --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/CodeModel.java @@ -0,0 +1,191 @@ +package cn.dev33.satoken.oauth2.model; + +/** + * Model: [授权码 - 数据 对应关系] + * @author kong + * + */ +public class CodeModel { + + /** + * 授权码 + */ + private String code; + + /** + * 应用id + */ + private String clientId; + + /** + * 授权范围 + */ + private String scope; + + /** + * 对应账号id + */ + private Object loginId; + + /** + * 用户是否已经确认了这个授权 + */ + private Boolean isConfirm; + + /** + * 确认授权后重定向的地址 + */ + private String redirectUri; + + /** + * 拒绝授权后重定向的地址 + */ + private String rejectUri; + + + /** + * 其他自定义数据 + */ + private Object tag; + + + /** + * 构建一个 + */ + public CodeModel() { + + } + /** + * 构建一个 + * @param code 授权码 + * @param clientId 应用id + * @param scope 请求授权范围 + * @param loginId 对应的账号id + */ + public CodeModel(String code, String clientId, String scope, Object loginId) { + super(); + this.code = code; + this.clientId = clientId; + this.scope = scope; + this.loginId = loginId; + this.isConfirm = false; + } + + + + /** + * @return code + */ + public String getCode() { + return code; + } + + /** + * @param code 要设置的 code + */ + public void setCode(String code) { + this.code = code; + } + + /** + * @return clientId + */ + public String getClientId() { + return clientId; + } + + /** + * @param clientId 要设置的 clientId + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * @return scope + */ + public String getScope() { + return scope; + } + + /** + * @param scope 要设置的 scope + */ + public void setScope(String scope) { + this.scope = scope; + } + + /** + * @return loginId + */ + public Object getLoginId() { + return loginId; + } + + /** + * @param loginId 要设置的 loginId + */ + public void setLoginId(Object loginId) { + this.loginId = loginId; + } + + /** + * @return isConfirm + */ + public Boolean getIsConfirm() { + return isConfirm; + } + + /** + * @param isConfirm 要设置的 isConfirm + */ + public void setIsConfirm(Boolean isConfirm) { + this.isConfirm = isConfirm; + } + + /** + * @return redirectUri + */ + public String getRedirectUri() { + return redirectUri; + } + + /** + * @param redirectUri 要设置的 redirectUri + */ + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + /** + * @return rejectUri + */ + public String getRejectUri() { + return rejectUri; + } + /** + * @param rejectUri 要设置的 rejectUri + */ + public void setRejectUri(String rejectUri) { + this.rejectUri = rejectUri; + } + + /** + * @return tag + */ + public Object getTag() { + return tag; + } + + /** + * @param tag 要设置的 tag + */ + public void setTag(Object tag) { + this.tag = tag; + } + + + + + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/RequestAuthModel.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/RequestAuthModel.java new file mode 100644 index 00000000..4b30bfb0 --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/RequestAuthModel.java @@ -0,0 +1,153 @@ +package cn.dev33.satoken.oauth2.model; + +import cn.dev33.satoken.exception.SaTokenException; +import cn.dev33.satoken.util.SaTokenInsideUtil; + +/** + * 请求授权参数的Model + * @author kong + * + */ +public class RequestAuthModel { + + /** + * 应用id + */ + private String clientId; + + /** + * 授权范围 + */ + private String scope; + + /** + * 对应的账号id + */ + private Object loginId; + + /** + * 待重定向URL + */ + private String redirectUri; + + /** + * 授权类型, 非必填 + */ + private String responseType; + + /** + * 状态标识, 可为null + */ + private String state; + + + /** + * @return clientId + */ + public String getClientId() { + return clientId; + } + + /** + * @param clientId 要设置的 clientId + */ + public RequestAuthModel setClientId(String clientId) { + this.clientId = clientId; + return this; + } + + /** + * @return scope + */ + public String getScope() { + return scope; + } + + /** + * @param scope 要设置的 scope + */ + public RequestAuthModel setScope(String scope) { + this.scope = scope; + return this; + } + + /** + * @return loginId + */ + public Object getLoginId() { + return loginId; + } + + /** + * @param loginId 要设置的 loginId + */ + public RequestAuthModel setLoginId(Object loginId) { + this.loginId = loginId; + return this; + } + + /** + * @return redirectUri + */ + public String getRedirectUri() { + return redirectUri; + } + + /** + * @param redirectUri 要设置的 redirectUri + */ + public RequestAuthModel setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + return this; + } + + /** + * @return responseType + */ + public String getResponseType() { + return responseType; + } + + /** + * @param responseType 要设置的 responseType + */ + public RequestAuthModel setResponseType(String responseType) { + this.responseType = responseType; + return this; + } + + /** + * @return state + */ + public String getState() { + return state; + } + + /** + * @param state 要设置的 state + */ + public RequestAuthModel setState(String state) { + this.state = state; + return this; + } + + /** + * 检查此Model参数是否有效 + */ + public RequestAuthModel checkModel() { + if(SaTokenInsideUtil.isEmpty(clientId)) { + throw new SaTokenException("无效client_id"); + } + if(SaTokenInsideUtil.isEmpty(scope)) { + throw new SaTokenException("无效scope"); + } + if(SaTokenInsideUtil.isEmpty(redirectUri)) { + throw new SaTokenException("无效redirect_uri"); + } + if(SaTokenInsideUtil.isEmpty(String.valueOf(loginId))) { + throw new SaTokenException("无效LoginId"); + } + return this; + } + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/ScopeModel.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/ScopeModel.java new file mode 100644 index 00000000..a994eee2 --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/model/ScopeModel.java @@ -0,0 +1,73 @@ +package cn.dev33.satoken.oauth2.model; + +/** + * 权限Model + * @author kong + * + */ +public class ScopeModel { + + /** + * 权限名称 + */ + private String name; + + /** + * 详细介绍 + */ + private String introduce; + + + /** + * 构造一个 + */ + public ScopeModel() { + super(); + } + /** + * 构造一个 + * @param id 权限id + * @param introduce 权限详细介绍 + */ + public ScopeModel(String name, String introduce) { + super(); + this.name = name; + this.introduce = introduce; + } + + /** + * @return name + */ + public String getName() { + return name; + } + + /** + * @param name 要设置的 name + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return introduce + */ + public String getIntroduce() { + return introduce; + } + + /** + * @param introduce 要设置的 introduce + */ + public void setIntroduce(String introduce) { + this.introduce = introduce; + } + + + + + + + + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/util/SaOAuth2Consts.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/util/SaOAuth2Consts.java new file mode 100644 index 00000000..129bb2f2 --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/util/SaOAuth2Consts.java @@ -0,0 +1,19 @@ +package cn.dev33.satoken.oauth2.util; + +/** + * sa-token oauth2 模块 用到的所有常量 + * @author kong + * + */ +public class SaOAuth2Consts { + + /** + * 在保存授权码时用到的key + */ + public static final String UNLIMITED_DOMAIN = "*"; + + + + + +} diff --git a/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/util/SaOAuth2InsideUtil.java b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/util/SaOAuth2InsideUtil.java new file mode 100644 index 00000000..fdd3b6d0 --- /dev/null +++ b/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/util/SaOAuth2InsideUtil.java @@ -0,0 +1,28 @@ +package cn.dev33.satoken.oauth2.util; + +/** + * sa-token-oauth2 模块内部算法util + * @author kong + * + */ +public class SaOAuth2InsideUtil { + + /** + * 验证URL的正则表达式 + */ + static final String URL_REGEX = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]"; + + /** + * 使用正则表达式判断一个字符串是否为URL + * @param str 字符串 + * @return 拼接后的url字符串 + */ + public static boolean isUrl(String str) { + if(str == null) { + return false; + } + return str.toLowerCase().matches(URL_REGEX); + } + + +}