实现 OAuth2.0 模块

This commit is contained in:
shengzhang 2021-02-17 18:07:45 +08:00
parent 1df97edaf8
commit 7e0f592a0f
48 changed files with 4692 additions and 128 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ bin/
unpackage/
.classpath
.project
*.iml
.factorypath
/.factorypath

View File

@ -23,6 +23,7 @@
<module>sa-token-dao-redis</module>
<module>sa-token-dao-redis-jackson</module>
<module>sa-token-spring-aop</module>
<module>sa-token-oauth2</module>
</modules>
<!-- 开源协议 apache 2.0 -->

View File

@ -9,4 +9,5 @@ unpackage/
.factorypath
.idea/
.idea/
.iml

View File

@ -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);
}
// --------------------- 会话管理 ---------------------

View File

@ -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<String> searchData(String prefix, String keyword, int start, int size) {
return SaTokenInsideUtil.searchList(expireMap.keySet(), prefix, keyword, start, size);
}

View File

@ -32,4 +32,14 @@ public class SaTokenException extends RuntimeException {
super(cause);
}
/**
* 构建一个异常
*
* @param message 异常信息
* @param cause 异常对象
*/
public SaTokenException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -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;
}

View File

@ -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;

View File

@ -9,4 +9,5 @@ unpackage/
.factorypath
.idea/
.idea/
.iml

View File

@ -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<String, SaSession> sessionRedisTemplate;
public RedisTemplate<String, Object> 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<String, SaSession> template = new RedisTemplate<String, SaSession>();
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
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);
}
/**
* 搜索数据
*/

View File

@ -9,4 +9,5 @@ unpackage/
.factorypath
.idea/
.idea/
.iml

View File

@ -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<String, SaSession> sessionRedisTemplate;
public RedisTemplate<String, Object> objectRedisTemplate;
@Autowired
public void setSessionRedisTemplate(RedisConnectionFactory connectionFactory) {
public void setObjectRedisTemplate(RedisConnectionFactory connectionFactory) {
// 指定相应的序列化方案
StringRedisSerializer keySerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer();
// 构建RedisTemplate
RedisTemplate<String, SaSession> template = new RedisTemplate<String, SaSession>();
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
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);
}

View File

@ -0,0 +1,13 @@
target/
.project
.classpath
.settings
/.idea/
node_modules/
bin/
.settings/
unpackage/
/.apt_generated/
/.apt_generated_tests/

View File

@ -0,0 +1,54 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pj</groupId>
<artifactId>sa-token-demo-oauth2-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<!-- 指定一些属性 -->
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties>
<dependencies>
<!-- springboot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sa-token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.13.0</version>
</dependency>
<!-- OkHttps网络请求库 http://okhttps.ejlchina.com/ -->
<dependency>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
<version>2.4.5</version>
</dependency>
<!-- ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -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");
}
}

View File

@ -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 以及其他信息
// 携带三个关键参数: codeclient_idclient_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;
}
}

View File

@ -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;
/**
* 全局异常拦截
* <p> @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;
}
}

View File

@ -0,0 +1,223 @@
package com.pj.utils;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* ajax请求返回Json格式数据的封装 <br>
* 所有预留字段<br>
* code=状态码 <br>
* msg=描述信息 <br>
* data=携带对象 <br>
* pageNo=当前页 <br>
* pageSize=页大小 <br>
* startIndex=起始索引 <br>
* dataCount=数据总数 <br>
* pageCount=分页总数 <br>
* <p> 返回范例</p>
* <pre>
{
"code": 200, // 成功时=200, 失败时=500 msg=失败原因
"msg": "ok",
"data": {}
}
</pre>
*/
public class AjaxJson extends LinkedHashMap<String, Object> 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> T getData(Class<T> 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<String, ?> 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=okfalse=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; // 数据总数用于分页
}

View File

@ -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类型但是它写着麻烦
* <p>所以特封装此类继承Map进行一些扩展可以让Map更灵活使用
* <p>最新2020-12-10 新增部分构造方法
* @author kong
*/
public class SoMap extends LinkedHashMap<String, Object> {
private static final long serialVersionUID = 1L;
public SoMap() {
}
/** 以下元素会在isNull函数中被判定为Null */
public static final Object[] NULL_ELEMENT_ARRAY = {null, ""};
public static final List<Object> 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<Object> getList(String key) {
Object value = get(key);
List<Object> list = null;
if(value == null || value.equals("")) {
list = new ArrayList<Object>();
}
else if(value instanceof List) {
list = (List<Object>)value;
} else {
list = new ArrayList<Object>();
list.add(value);
}
return list;
}
/** 获取集合 (指定泛型类型) */
public <T> List<T> getList(String key, Class<T> cs) {
List<Object> list = getList(key);
List<T> list2 = new ArrayList<T>();
for (Object obj : list) {
T objC = getValueByClass(obj, cs);
list2.add(objC);
}
return list2;
}
/** 获取集合(逗号分隔式)(指定类型) */
public <T> List<T> getListByComma(String key, Class<T> cs) {
String listStr = getString(key);
if(listStr == null || listStr.equals("")) {
return new ArrayList<>();
}
// 开始转化
String [] arr = listStr.split(",");
List<T> list = new ArrayList<T>();
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> T getModel(Class<T> cs) {
try {
return getModelByObject(cs.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** 从map中取值塞到一个对象中 */
public <T> 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 <T>
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getValueByClass(Object obj, Class<T> 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<String, ?> 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<String, Object> 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<String> 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<String> keys2 = Arrays.asList(keys);
Iterator<String> 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<String> keys2 = Arrays.asList(keys);
Iterator<String> 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<String, ?> map) {
return new SoMap().setMap(map);
}
/** 将一个对象集合解析成为SoMap */
public static SoMap getSoMapByModel(Object model) {
return SoMap.getSoMap().setModel(model);
}
/** 将一个对象集合解析成为SoMap集合 */
public static List<SoMap> getSoMapByList(List<?> list) {
List<SoMap> listMap = new ArrayList<SoMap>();
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<String, String[]> 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<String> list = new ArrayList<String>();
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<SoMap> listToTree(List<SoMap> list, String idKey, String parentIdKey, String childListKey) {
// 声明新的集合存储tree形数据
List<SoMap> newTreeList = new ArrayList<SoMap>();
// 声明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<SoMap> ch = (List<SoMap>) hashVp.get(childListKey);
ch.add(aVal);
hashVp.put(childListKey, ch);
} else {
List<SoMap> ch = new ArrayList<SoMap>();
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();
}
}

View File

@ -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

View File

@ -0,0 +1,127 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>客户端-登录页</title>
<style type="text/css">
*{margin: 0px; padding: 0px;}
.login-box{width: 500px; margin: 50px auto;}
</style>
</head>
<body>
<div class="login-box">
<h2>客户端-登录页</h2> <br>
<div>
当前是否登录: <span class="login-info"></span>
<button onclick="logout()">注销登录</button>
</div>
<br/>
点此按钮开始使用OAuth2.0开放平台快捷登录:
<button onclick="requestOAuth2()">快捷登录</button>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// 获取登录信息
var currUserId = null;
function getLoginInfo() {
$.ajax({
url: '/getLoginInfo',
data: {},
dataType: 'json',
success: function(res) {
if(res.data == null) {
$(".login-info").html('<b style="color: red;">未登录</b>');
// 如果当前未登录并且此时url中有code参数, 则尝试使用code码进行登录
var code = getParam('code', null);
if(code != null) {
doCodeLogin();
}
} else {
$(".login-info").html('<b style="color: green;">已登录, userId=' + res.data + '</b>');
currUserId = res.data;
}
},
error: function(e, ee, eee) {
console.log('error');
alert(eee);
}
});
}
getLoginInfo();
// 注销登录
function logout() {
$.ajax({
url: '/logout',
dataType: 'json',
success: function(res) {
alert('注销成功');
location.href = "./login.html";
},
error: function(e, ee, eee) {
alert(eee);
}
});
}
// 请求OAuth2授权
function requestOAuth2() {
// 如果当前已经登录,则必须先退出
if(currUserId != null) {
alert('当前已经登录, 请先注销');
return;
}
// 拼接地址
var url = "http://localhost:8001/oauth2/authorize" +
"?client_id=123123123" + // 应用id
"&scope=userinfo" + // 授权范围
// "&redirect_uri=" + encodeURIComponent(location.href) // 重定向地址
"&redirect_uri=" + encodeURIComponent("http://localhost:8002/login.html") // 重定向地址
"&response_type=code" + // 返回格式
"&state=123456789"; // 授权范围
console.log(url);
location.href = url;
}
// 使用code进行 (从url中获取code码进行登录)
function doCodeLogin() {
var code = getParam('code');
$.ajax({
url: '/doCodeLogin',
data: {
code: code
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
alert('OAuth2登录成功');
getLoginInfo(); // 刷新user信息
} else {
alert("登录失败:" + res.msg);
}
},
error: function(e, ee, eee) {
alert(eee);
}
});
}
// 从url中查询到指定名称的参数值
function getParam(name, defaultValue){
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == name){return pair[1];}
}
return(defaultValue == undefined ? null : defaultValue);
}
</script>
</body>
</html>

View File

@ -0,0 +1,13 @@
target/
.project
.classpath
.settings
/.idea/
node_modules/
bin/
.settings/
unpackage/
/.apt_generated/
/.apt_generated_tests/

View File

@ -0,0 +1,66 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pj</groupId>
<artifactId>sa-token-demo-oauth2-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<!-- 指定一些属性 -->
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties>
<dependencies>
<!-- springboot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- sa-token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.13.0</version>
</dependency>
<!-- sa-token 实现 oauth2.0 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-oauth2</artifactId>
<version>1.13.0-alpha</version>
</dependency>
<!-- sa-token整合redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@ -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服务端启动成功");
}
}

View File

@ -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;
/**
* 全局异常拦截
* <p> @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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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<String> getAppScopeList() {
return Arrays.asList("userinfo");
}
// 返回指定Client签约的所有Scope集合
@Override
public List<String> getClientScopeList(String clientId) {
return Arrays.asList("userinfo");
}
// 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope
@Override
public List<String> 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;
}
/*
* 以上函数为开发时必须重写实现其余函数可以按需重写
*/
}

View File

@ -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);
}
}

View File

@ -0,0 +1,223 @@
package com.pj.utils;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* ajax请求返回Json格式数据的封装 <br>
* 所有预留字段<br>
* code=状态码 <br>
* msg=描述信息 <br>
* data=携带对象 <br>
* pageNo=当前页 <br>
* pageSize=页大小 <br>
* startIndex=起始索引 <br>
* dataCount=数据总数 <br>
* pageCount=分页总数 <br>
* <p> 返回范例</p>
* <pre>
{
"code": 200, // 成功时=200, 失败时=500 msg=失败原因
"msg": "ok",
"data": {}
}
</pre>
*/
public class AjaxJson extends LinkedHashMap<String, Object> 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> T getData(Class<T> 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<String, ?> 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=okfalse=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; // 数据总数用于分页
}

View File

@ -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类型但是它写着麻烦
* <p>所以特封装此类继承Map进行一些扩展可以让Map更灵活使用
* <p>最新2020-12-10 新增部分构造方法
* @author kong
*/
public class SoMap extends LinkedHashMap<String, Object> {
private static final long serialVersionUID = 1L;
public SoMap() {
}
/** 以下元素会在isNull函数中被判定为Null */
public static final Object[] NULL_ELEMENT_ARRAY = {null, ""};
public static final List<Object> 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<Object> getList(String key) {
Object value = get(key);
List<Object> list = null;
if(value == null || value.equals("")) {
list = new ArrayList<Object>();
}
else if(value instanceof List) {
list = (List<Object>)value;
} else {
list = new ArrayList<Object>();
list.add(value);
}
return list;
}
/** 获取集合 (指定泛型类型) */
public <T> List<T> getList(String key, Class<T> cs) {
List<Object> list = getList(key);
List<T> list2 = new ArrayList<T>();
for (Object obj : list) {
T objC = getValueByClass(obj, cs);
list2.add(objC);
}
return list2;
}
/** 获取集合(逗号分隔式)(指定类型) */
public <T> List<T> getListByComma(String key, Class<T> cs) {
String listStr = getString(key);
if(listStr == null || listStr.equals("")) {
return new ArrayList<>();
}
// 开始转化
String [] arr = listStr.split(",");
List<T> list = new ArrayList<T>();
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> T getModel(Class<T> cs) {
try {
return getModelByObject(cs.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** 从map中取值塞到一个对象中 */
public <T> 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 <T>
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getValueByClass(Object obj, Class<T> 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<String, ?> 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<String, Object> 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<String> 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<String> keys2 = Arrays.asList(keys);
Iterator<String> 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<String> keys2 = Arrays.asList(keys);
Iterator<String> 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<String, ?> map) {
return new SoMap().setMap(map);
}
/** 将一个对象集合解析成为SoMap */
public static SoMap getSoMapByModel(Object model) {
return SoMap.getSoMap().setModel(model);
}
/** 将一个对象集合解析成为SoMap集合 */
public static List<SoMap> getSoMapByList(List<?> list) {
List<SoMap> listMap = new ArrayList<SoMap>();
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<String, String[]> 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<String> list = new ArrayList<String>();
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<SoMap> listToTree(List<SoMap> list, String idKey, String parentIdKey, String childListKey) {
// 声明新的集合存储tree形数据
List<SoMap> newTreeList = new ArrayList<SoMap>();
// 声明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<SoMap> ch = (List<SoMap>) hashVp.get(childListKey);
ch.add(aVal);
hashVp.put(childListKey, ch);
} else {
List<SoMap> ch = new ArrayList<SoMap>();
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();
}
}

View File

@ -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

View File

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>服务提供方-确认授权页</title>
<style type="text/css">
*{margin: 0px; padding: 0px;}
.login-box{width: 300px; margin: 50px auto; padding: 100px; border: 1px #000 solid;}
</style>
</head>
<body>
<div class="login-box">
<h2>服务提供方-确认授权页</h2> <br>
<div>
<div>应用id: <span class="client_id"></span></div>
<div>请求授权: <span class="scope"></span></div>
<br>
<div>是否同意授权: </div>
<div>
<button onclick="ok()">同意</button>
<button onclick="no()">拒绝</button>
</div>
</div>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// 获取code详细信息
var code = getParam("code", null);
var codeObj = null;
if(code == null) {
throw alert("无效code");
}
$.ajax({
url: '/oauth2/getCodeInfo',
data: {code: code},
dataType: 'json',
success: function(res) {
if(res.data == null) {
return alert('无效授权码');
}
codeObj = res.data;
$(".client_id").html(res.data.clientId);
$(".scope").html(res.data.scope);
},
error: function(e, ee, eee) {
console.log('error');
alert(eee);
}
});
// 确认授权
function ok() {
$.ajax({
url: '/oauth2/confirm',
data: {code: code},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
// 跳转
location.href = codeObj.redirectUri;
} else {
alert(res.msg);
}
},
error: function(e, ee, eee) {
alert(eee);
}
});
}
// 拒绝授权时,跳入拒绝授权地址
function no() {
// alert(codeObj.rejectUri)
location.href = codeObj.rejectUri;
}
// 从url中查询到指定名称的参数值
function getParam(name, defaultValue){
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == name){return pair[1];}
}
return(defaultValue == undefined ? null : defaultValue);
}
</script>
</body>
</html>

View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>服务提供方-登录页</title>
<style type="text/css">
*{margin: 0px; padding: 0px;}
.login-box{width: 400px; margin: 50px auto;}
</style>
</head>
<body>
<div class="login-box">
<h2>服务提供方-登录页</h2> <br>
<div>注:您当前在服务提供方尚未登录,请先登录</div>
<div>测试账号: test test</div> <br>
账号:<input name="username" /> <br>
密码:<input name="password" type="password" /> <br>
<button onclick="doLogin()">登录</button>
</div>
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
<script type="text/javascript">
// 登录方法
function getLoginId() {
}
// 登录方法
function doLogin() {
console.log('-----------');
$.ajax({
url: '/doLogin',
data: {
username: $('[name=username]').val(),
password: $('[name=password]').val()
},
dataType: 'json',
success: function(res) {
if(res.code == 200) {
alert('登录成功');
location.reload(true);
} else {
alert('登录失败');
}
},
error: function(e) {
console.log('error');
}
});
}
</script>
</body>
</html>

13
sa-token-oauth2/.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.factorypath
.idea/
.iml

11
sa-token-oauth2/README.md Normal file
View File

@ -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)
可结合代码注释学习查看

30
sa-token-oauth2/pom.xml Normal file
View File

@ -0,0 +1,30 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.13.0</version>
</parent>
<packaging>jar</packaging>
<name>sa-token-dao-redis</name>
<artifactId>sa-token-oauth2</artifactId>
<version>1.13.0-alpha</version>
<description>sa-token realization oauth2.0</description>
<dependencies>
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>${sa-token-version}</version>
</dependency>
</dependencies>
</project>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<String> getAppScopeList() {
return Arrays.asList("userinfo");
}
/**
* [default] 返回指定Client签约的所有Scope名称集合
* @param clientId 应用id
* @return Scope集合
*/
public default List<String> getClientScopeList(String clientId) {
// 默认返回此APP的所有权限
return getAppScopeList();
}
/**
* [default] 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope
* @param clientId 应用id
* @param loginId 账号id
* @return Scope集合
*/
public default List<String> 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<String> clientScopeList = getClientScopeList(clientId);
List<String> 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<String> grantScopeList = getGrantScopeList(loginId, clientId);
List<String> 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<String> domainList = convertStringToList(domain);
if(domainList.contains(host) == false) {
throw new SaTokenException("重定向地址不在列表中");
}
} catch (MalformedURLException e) {
throw new SaTokenException("url格式错误", e);
}
}
/**
* [default] 校验codeclientIdclientSecret 三者是否正确
* @param code 授权码
* @param clientId 应用id
* @param clientSecret 秘钥
* @return CodeModel对象
*/
public default CodeModel checkCodeIdSecret(String code, String clientId, String clientSecret) {
// 获取授权码信息
CodeModel codeModel = getCode(code);
// 验证codeclient_idclient_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_tokenclientIdclientSecret 三者是否正确
* @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);
// 验证codeclient_idclient_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<String> 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;
}
/**
* 获取keyrefreshToken持久化使用的key
* @param refreshToken refreshToken
* @return key
*/
public default String getKeyRefreshToken(String refreshToken) {
return SaTokenManager.getConfig().getTokenName() + ":oauth2:refresh-token:" + refreshToken;
}
/**
* 获取keyaccessToken持久化使用的key
* @param accessToken accessToken
* @return key
*/
public default String getKeyAccessToken(String accessToken) {
return SaTokenManager.getConfig().getTokenName() + ":oauth2:access-token:" + accessToken;
}
}

View File

@ -0,0 +1,12 @@
package cn.dev33.satoken.oauth2.logic;
/**
* SaOAuth2Interface 默认实现类 (只构建userinfo单个权限)
* @author kong
*
*/
public class SaOAuth2InterfaceDefaultImpl implements SaOAuth2Interface {
}

View File

@ -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<String> getAppScopeList() {
return SaOAuth2Manager.getInterface().getAppScopeList();
}
/**
* 返回指定Client签约的所有Scope名称集合
* @param clientId 应用id
* @return Scope集合
*/
public static List<String> getClientScopeList(String clientId) {
return SaOAuth2Manager.getInterface().getClientScopeList(clientId);
}
/**
* 获取指定 LoginId 对指定 Client 已经授权过的所有 Scope
* @param clientId 应用id
* @param loginId 账号id
* @return Scope集合
*/
public static List<String> 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);
}
/**
* 校验codeclientIdclientSecret 三者是否正确
* @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_tokenclientIdclientSecret 三者是否正确
* @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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 = "*";
}

View File

@ -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);
}
}