diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/DefaultUserTokenManager.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/DefaultUserTokenManager.java index 35a36adbe..89519df66 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/DefaultUserTokenManager.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/DefaultUserTokenManager.java @@ -18,6 +18,8 @@ package org.hswebframework.web.authorization.token; +import org.hswebframework.web.authorization.exception.AccessDenyException; +import org.hswebframework.web.authorization.exception.UnAuthorizedException; import org.hswebframework.web.authorization.token.event.UserTokenChangedEvent; import org.hswebframework.web.authorization.token.event.UserTokenCreatedEvent; import org.hswebframework.web.authorization.token.event.UserTokenRemovedEvent; @@ -32,7 +34,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; /** - * 授权容器,用来操作所有已经授权的用户 + * 默认到用户令牌管理器,使用ConcurrentMap来存储令牌信息 * * @author zhouhao * @since 3.0 @@ -41,7 +43,7 @@ public class DefaultUserTokenManager implements UserTokenManager { protected final ConcurrentMap tokenUserStorage; - protected ConcurrentMap> userStorage; + protected final ConcurrentMap> userStorage; public DefaultUserTokenManager() { this(new ConcurrentHashMap<>(256)); @@ -52,10 +54,9 @@ public class DefaultUserTokenManager implements UserTokenManager { this(storage, new ConcurrentHashMap<>()); } - public DefaultUserTokenManager(ConcurrentMap storage, ConcurrentMap> userStorage) { + public DefaultUserTokenManager(ConcurrentMap storage, ConcurrentMap> userStorage) { this.tokenUserStorage = storage; this.userStorage = userStorage; - } //异地登录模式,默认允许异地登录 @@ -77,8 +78,8 @@ public class DefaultUserTokenManager implements UserTokenManager { return allopatricLoginMode; } - protected List getUserToken(String userId) { - return userStorage.computeIfAbsent(userId, key -> new ArrayList<>()); + protected Set getUserToken(String userId) { + return userStorage.computeIfAbsent(userId, key -> new HashSet<>()); } private SimpleUserToken checkTimeout(SimpleUserToken detail) { @@ -159,18 +160,18 @@ public class DefaultUserTokenManager implements UserTokenManager { if (null == userId) { return; } - List tokens = getUserToken(userId); - tokens.forEach(token->signOutByToken(token,false)); + Set tokens = getUserToken(userId); + tokens.forEach(token -> signOutByToken(token, false)); tokens.clear(); userStorage.remove(userId); } - private void signOutByToken(String token,boolean removeUserToken) { + private void signOutByToken(String token, boolean removeUserToken) { SimpleUserToken tokenObject = tokenUserStorage.remove(token); if (tokenObject != null) { String userId = tokenObject.getUserId(); - if(removeUserToken) { - List tokens = getUserToken(userId); + if (removeUserToken) { + Set tokens = getUserToken(userId); if (tokens.size() > 0) { tokens.remove(token); } @@ -184,7 +185,7 @@ public class DefaultUserTokenManager implements UserTokenManager { @Override public void signOutByToken(String token) { - signOutByToken(token,true); + signOutByToken(token, true); } protected void publishEvent(ApplicationEvent event) { @@ -221,17 +222,22 @@ public class DefaultUserTokenManager implements UserTokenManager { detail.setMaxInactiveInterval(maxInactiveInterval); if (allopatricLoginMode == AllopatricLoginMode.deny) { - changeTokenState(detail.getToken(), TokenState.deny); + boolean hasAnotherToken = getByUserId(userId) + .stream() + .map(SimpleUserToken.class::cast) + .peek(this::checkTimeout) + .anyMatch(UserToken::isEffective); + if (hasAnotherToken) { + throw new AccessDenyException("该用户已在其他地方登陆"); + } } else if (allopatricLoginMode == AllopatricLoginMode.offlineOther) { - detail.setState(TokenState.effective); - //将已经登录的用户设置为离线 + //将在其他地方登录的用户设置为离线 List oldToken = getByUserId(userId); for (UserToken userToken : oldToken) { changeTokenState(userToken.getToken(), TokenState.offline); } - } else { - detail.setState(TokenState.effective); } + detail.setState(TokenState.effective); tokenUserStorage.put(token, detail); getUserToken(userId).add(token); @@ -259,6 +265,11 @@ public class DefaultUserTokenManager implements UserTokenManager { } } + /** + * 同步令牌信息,如果使用redisson等来存储token,应该重写此方法并调用{@link this#tokenUserStorage}.put + * + * @param userToken 令牌 + */ protected void syncToken(UserToken userToken) { //do noting } diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenHolder.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenHolder.java index 47c1dbdd8..796b0cf85 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenHolder.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenHolder.java @@ -4,8 +4,6 @@ import org.hswebframework.web.ThreadLocalUtils; import org.hswebframework.web.authorization.token.UserToken; /** - * TODO 完成注释 - * * @author zhouhao */ public final class UserTokenHolder { diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenManager.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenManager.java index 1a36b4fb6..e72800cef 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenManager.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenManager.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.function.Consumer; /** - * 用户授权容器,用来操作所有已经授权的用户 + * 用户令牌管理器,用于管理用户令牌 * * @author zhouhao * @since 3.0 @@ -68,11 +68,17 @@ public interface UserTokenManager { long totalToken(); /** - * @return 所有被授权的用户 + * @return 所有token */ List allLoggedUser(); + /** + * 遍历全部token信息 + * + * @param consumer token消费者 + */ void allLoggedUser(Consumer consumer); + /** * 删除用户授权信息 * @@ -83,7 +89,8 @@ public interface UserTokenManager { /** * 根据token删除 * - * @param token + * @param token 令牌 + * @see org.hswebframework.web.authorization.token.event.UserTokenRemovedEvent */ void signOutByToken(String token); @@ -92,6 +99,8 @@ public interface UserTokenManager { * * @param userId userId * @param state 状态 + * @see org.hswebframework.web.authorization.token.event.UserTokenChangedEvent + * @see this#changeTokenState */ void changeUserState(String userId, TokenState state); @@ -100,15 +109,18 @@ public interface UserTokenManager { * * @param token token * @param state 状态 + * @see org.hswebframework.web.authorization.token.event.UserTokenChangedEvent */ void changeTokenState(String token, TokenState state); /** * 登记一个用户的token * - * @param token token - * @param type 令牌类型 - * @param userId 用户id + * @param token token + * @param type 令牌类型 + * @param userId 用户id + * @param maxInactiveInterval 最大不活动时间,超过后令牌状态{@link UserToken#getState()}将变为过期{@link TokenState#expired} + * @see org.hswebframework.web.authorization.token.event.UserTokenCreatedEvent */ UserToken signIn(String token, String type, String userId, long maxInactiveInterval); diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java index 1dfba0cb2..60b5e5b59 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/configuration/AuthorizingHandlerAutoConfiguration.java @@ -8,6 +8,7 @@ import org.hswebframework.web.authorization.access.DataAccessHandler; import org.hswebframework.web.authorization.basic.handler.DefaultAuthorizingHandler; import org.hswebframework.web.authorization.basic.handler.access.DefaultDataAccessController; import org.hswebframework.web.authorization.basic.web.*; +import org.hswebframework.web.authorization.basic.web.session.UserTokenAutoExpiredListener; import org.hswebframework.web.authorization.token.DefaultUserTokenManager; import org.hswebframework.web.authorization.token.UserTokenAuthenticationSupplier; import org.hswebframework.web.authorization.token.UserTokenManager; @@ -87,6 +88,11 @@ public class AuthorizingHandlerAutoConfiguration { return new UserOnSignOut(userTokenManager); } + @Bean + public UserTokenAutoExpiredListener userTokenAutoExpiredListener(UserTokenManager userTokenManager) { + return new UserTokenAutoExpiredListener(userTokenManager); + } + @Configuration public static class DataAccessHandlerProcessor implements BeanPostProcessor { diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizedToken.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizedToken.java index 6c2c67367..dacc1cc3a 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizedToken.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizedToken.java @@ -6,8 +6,15 @@ package org.hswebframework.web.authorization.basic.web; * @author zhouhao */ public interface AuthorizedToken extends ParsedToken { + + /** + * @return 令牌绑定的用户id + */ String getUserId(); + /** + * @return 令牌有效期,单位毫秒,-1为长期有效 + */ default long getMaxInactiveInterval() { return -1; } diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/ParsedToken.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/ParsedToken.java index 874a9a673..5aa7743d9 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/ParsedToken.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/ParsedToken.java @@ -1,12 +1,18 @@ package org.hswebframework.web.authorization.basic.web; /** - * TODO 完成注释 + * 令牌解析结果 * * @author zhouhao */ public interface ParsedToken { + /** + * @return 令牌 + */ String getToken(); + /** + * @return 令牌类型 + */ String getType(); } diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/SessionIdUserTokenGenerator.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/SessionIdUserTokenGenerator.java index 20f7a565e..ba06fb27c 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/SessionIdUserTokenGenerator.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/SessionIdUserTokenGenerator.java @@ -13,9 +13,11 @@ import java.util.Map; */ public class SessionIdUserTokenGenerator implements UserTokenGenerator, Serializable { + private static final long serialVersionUID = -9197243220777237431L; + @Override public String getSupportTokenType() { - return "sessionId"; + return TOKEN_TYPE_SESSION_ID; } @Override @@ -30,6 +32,8 @@ public class SessionIdUserTokenGenerator implements UserTokenGenerator, Serializ String sessionId = request.getSession().getId(); return new GeneratedToken() { + private static final long serialVersionUID = 3964183451883410929L; + @Override public Map getResponse() { return Collections.emptyMap(); @@ -42,7 +46,7 @@ public class SessionIdUserTokenGenerator implements UserTokenGenerator, Serializ @Override public String getType() { - return "session-id-default"; + return TOKEN_TYPE_SESSION_ID; } @Override diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/SessionIdUserTokenParser.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/SessionIdUserTokenParser.java index e56b581e5..f594d25c5 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/SessionIdUserTokenParser.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/SessionIdUserTokenParser.java @@ -8,6 +8,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.function.Predicate; +import static org.hswebframework.web.authorization.basic.web.UserTokenGenerator.TOKEN_TYPE_SESSION_ID; + /** * @author zhouhao */ @@ -46,7 +48,7 @@ public class SessionIdUserTokenParser implements UserTokenParser { @Override public String getType() { - return "session-id-default"; + return TOKEN_TYPE_SESSION_ID; } @Override @@ -63,7 +65,7 @@ public class SessionIdUserTokenParser implements UserTokenParser { @Override public String getType() { - return "session-id-default"; + return TOKEN_TYPE_SESSION_ID; } }; } diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/UserOnSignIn.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/UserOnSignIn.java index 068d816c6..d320f1230 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/UserOnSignIn.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/UserOnSignIn.java @@ -12,13 +12,28 @@ import java.util.ArrayList; import java.util.List; /** + * 监听授权成功事件,授权成功后,生成token并注册到{@link UserTokenManager} + * * @author zhouhao + * @see org.springframework.context.ApplicationEvent + * @see org.hswebframework.web.authorization.listener.event.AuthorizationEvent + * @see UserTokenManager + * @see UserTokenGenerator + * @since 3.0 */ public class UserOnSignIn implements AuthorizationListener - ,ApplicationListener{ + , ApplicationListener { + /** + * 默认到令牌类型 + * @see UserToken#getType() + * @see SessionIdUserTokenGenerator#getSupportTokenType() + */ private String defaultTokenType = "sessionId"; + /** + * 令牌管理器 + */ private UserTokenManager userTokenManager; private List userTokenGenerators = new ArrayList<>(); @@ -38,7 +53,7 @@ public class UserOnSignIn implements AuthorizationListener repo = client.getMap("hsweb.user-token", new SerializationCodec()); - ConcurrentMap> userRepo = client.getMap("hsweb.user-token-u", new SerializationCodec()); + ConcurrentMap> userRepo = client.getMap("hsweb.user-token-u", new SerializationCodec()); userTokenManager = new DefaultUserTokenManager(repo, userRepo) { @Override - protected List getUserToken(String userId) { - userRepo.computeIfAbsent(userId,u->new ArrayList<>()); + protected Set getUserToken(String userId) { + userRepo.computeIfAbsent(userId,u->new HashSet<>()); - return client.getList("hsweb.user-token-"+userId, new SerializationCodec()); + return client.getSet("hsweb.user-token-"+userId, new SerializationCodec()); } }; + + userTokenManager.setAllopatricLoginMode(AllopatricLoginMode.deny); // userTokenManager=new DefaultUserTokenManager(); - userRepo.clear(); - repo.clear(); - for (int i = 0; i < 1000; i++) { - userTokenManager.signIn(IDGenerator.MD5.generate(), "sessionId", "admin", 60*3600*1000); - } - userTokenManager.signIn(IDGenerator.MD5.generate(), "sessionId", "admin2", 60*3600*1000); +// userRepo.clear(); +// repo.clear(); +// for (int i = 0; i < 1000; i++) { +// userTokenManager.signIn(IDGenerator.MD5.generate(), "sessionId", "admin", 60*3600*1000); +// } +// userTokenManager.signIn(IDGenerator.MD5.generate(), "sessionId", "admin2", 60*3600*1000); testGet(); testGetAll(); diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-controller/src/main/java/org/hswebframework/web/controller/authorization/AuthorizationController.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-controller/src/main/java/org/hswebframework/web/controller/authorization/AuthorizationController.java index c9a9be35b..37fc0bef1 100644 --- a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-controller/src/main/java/org/hswebframework/web/controller/authorization/AuthorizationController.java +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-controller/src/main/java/org/hswebframework/web/controller/authorization/AuthorizationController.java @@ -25,7 +25,6 @@ import org.hswebframework.web.NotFoundException; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationManager; import org.hswebframework.web.authorization.annotation.Authorize; -import org.hswebframework.web.authorization.listener.AuthorizationListenerDispatcher; import org.hswebframework.web.authorization.listener.event.*; import org.hswebframework.web.commons.entity.DataStatus; import org.hswebframework.web.controller.message.ResponseMessage;