diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java index 6921ea32c..c8e647c08 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java @@ -44,6 +44,14 @@ public @interface Authorize { Dimension[] dimension() default {}; + /** + * 是否运行匿名访问,匿名访问时,直接允许执行,否则将进行权限验证. + * + * @return 是否允许匿名访问 + * @since 4.0.19 + */ + boolean anonymous() default false; + /** * 验证失败时返回的消息 * diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinition.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinition.java index b1c4e8b00..8ab805bd1 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinition.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinition.java @@ -21,6 +21,10 @@ public interface AuthorizeDefinition { boolean isEmpty(); + default boolean allowAnonymous() { + return false; + } + default String getDescription() { ResourcesDefinition res = getResources(); StringJoiner joiner = new StringJoiner(";"); diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java index a391ec816..73ba11e2f 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java @@ -44,21 +44,21 @@ public class RedisUserTokenManager implements UserTokenManager { this.userTokenStore = operations.opsForHash(); this.userTokenMapping = operations.opsForSet(); this.operations - .listenToChannel("_user_token_removed") - .subscribe(msg -> localCache.remove(String.valueOf(msg.getMessage()))); + .listenToChannel("_user_token_removed") + .subscribe(msg -> localCache.remove(String.valueOf(msg.getMessage()))); Flux.create(sink -> this.touchSink = sink) .buffer(Flux.interval(Duration.ofSeconds(10)), HashSet::new) .flatMap(list -> Flux - .fromIterable(list) - .flatMap(token -> { - String key = getTokenRedisKey(token.getToken()); - return Mono - .zip(this.userTokenStore.put(key, "lastRequestTime", token.getLastRequestTime()), - this.operations.expire(key, Duration.ofMillis(token.getMaxInactiveInterval()))) - .then(); - }) - .onErrorResume(err -> Mono.empty())) + .fromIterable(list) + .flatMap(token -> { + String key = getTokenRedisKey(token.getToken()); + return Mono + .zip(this.userTokenStore.put(key, "lastRequestTime", token.getLastRequestTime()), + this.operations.expire(key, Duration.ofMillis(token.getMaxInactiveInterval()))) + .then(); + }) + .onErrorResume(err -> Mono.empty())) .subscribe(); } @@ -67,12 +67,12 @@ public class RedisUserTokenManager implements UserTokenManager { public RedisUserTokenManager(ReactiveRedisConnectionFactory connectionFactory) { this(new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext - .newSerializationContext() - .key((RedisSerializer) RedisSerializer.string()) - .value(RedisSerializer.java()) - .hashKey(RedisSerializer.string()) - .hashValue(RedisSerializer.java()) - .build() + .newSerializationContext() + .key((RedisSerializer) RedisSerializer.string()) + .value(RedisSerializer.java()) + .hashKey(RedisSerializer.string()) + .hashValue(RedisSerializer.java()) + .build() )); } @@ -107,82 +107,82 @@ public class RedisUserTokenManager implements UserTokenManager { return Mono.just(inCache); } return userTokenStore - .entries(getTokenRedisKey(token)) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) - .filter(map -> !map.isEmpty() && map.containsKey("token") && map.containsKey("userId")) - .map(SimpleUserToken::of) - .doOnNext(userToken -> localCache.put(userToken.getToken(), userToken)) - .cast(UserToken.class); + .entries(getTokenRedisKey(token)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) + .filter(map -> !map.isEmpty() && map.containsKey("token") && map.containsKey("userId")) + .map(SimpleUserToken::of) + .doOnNext(userToken -> localCache.put(userToken.getToken(), userToken)) + .cast(UserToken.class); } @Override public Flux getByUserId(String userId) { String redisKey = getUserRedisKey(userId); return userTokenMapping - .members(redisKey) - .map(String::valueOf) - .flatMap(token -> getByToken(token) - .switchIfEmpty(Mono.defer(() -> userTokenMapping - .remove(redisKey, token) - .then(Mono.empty())))); + .members(redisKey) + .map(String::valueOf) + .flatMap(token -> getByToken(token) + .switchIfEmpty(Mono.defer(() -> userTokenMapping + .remove(redisKey, token) + .then(Mono.empty())))); } @Override public Mono userIsLoggedIn(String userId) { return getByUserId(userId) - .any(UserToken::isNormal); + .any(UserToken::isNormal); } @Override public Mono tokenIsLoggedIn(String token) { return getByToken(token) - .map(UserToken::isNormal) - .defaultIfEmpty(false); + .map(UserToken::isNormal) + .defaultIfEmpty(false); } @Override public Mono totalUser() { return operations - .scan(ScanOptions - .scanOptions() - .match("*user-token-user:*") - .build()) - .count() - .map(Long::intValue); + .scan(ScanOptions + .scanOptions() + .match("*user-token-user:*") + .build()) + .count() + .map(Long::intValue); } @Override public Mono totalToken() { return operations - .scan(ScanOptions - .scanOptions() - .match("*user-token:*") - .build()) - .count() - .map(Long::intValue); + .scan(ScanOptions + .scanOptions() + .match("*user-token:*") + .build()) + .count() + .map(Long::intValue); } @Override public Flux allLoggedUser() { return operations - .scan(ScanOptions - .scanOptions() - .match("*user-token:*") - .build()) - .map(val -> String.valueOf(val).substring(11)) - .flatMap(this::getByToken); + .scan(ScanOptions + .scanOptions() + .match("*user-token:*") + .build()) + .map(val -> String.valueOf(val).substring(11)) + .flatMap(this::getByToken); } @Override public Mono signOutByUserId(String userId) { return this - .getByUserId(userId) - .flatMap(userToken -> operations - .delete(getTokenRedisKey(userToken.getToken())) - .then(onTokenRemoved(userToken))) - .then(operations.delete(getUserRedisKey(userId))) - .then(); + .getByUserId(userId) + .flatMap(userToken -> operations + .delete(getTokenRedisKey(userToken.getToken())) + .then(onTokenRemoved(userToken))) + .then(operations.delete(getUserRedisKey(userId))) + .then(); } @Override @@ -190,33 +190,33 @@ public class RedisUserTokenManager implements UserTokenManager { //delete token //srem user token return getByToken(token) - .flatMap(t -> operations - .delete(getTokenRedisKey(t.getToken())) - .then(userTokenMapping.remove(getUserRedisKey(t.getUserId()), token)) - .then(onTokenRemoved(t)) - ) - .then(); + .flatMap(t -> operations + .delete(getTokenRedisKey(t.getToken())) + .then(userTokenMapping.remove(getUserRedisKey(t.getUserId()), token)) + .then(onTokenRemoved(t)) + ) + .then(); } @Override public Mono changeUserState(String userId, TokenState state) { return getByUserId(userId) - .flatMap(token -> changeTokenState(token.getToken(), state)) - .then(); + .flatMap(token -> changeTokenState(token.getToken(), state)) + .then(); } @Override public Mono changeTokenState(String token, TokenState state) { return getByToken(token) - .flatMap(old -> { - SimpleUserToken newToken = FastBeanCopier.copy(old, new SimpleUserToken()); - newToken.setState(state); - return userTokenStore - .put(getTokenRedisKey(token), "state", state.getValue()) - .then(onTokenChanged(old, newToken)); - }); + .flatMap(old -> { + SimpleUserToken newToken = FastBeanCopier.copy(old, new SimpleUserToken()); + newToken.setState(state); + return userTokenStore + .put(getTokenRedisKey(token), "state", state.getValue()) + .then(onTokenChanged(old, newToken)); + }); } private Mono signIn(String token, @@ -228,56 +228,56 @@ public class RedisUserTokenManager implements UserTokenManager { long expires = maxTokenExpires.isNegative() ? maxInactiveInterval : Math.min(maxInactiveInterval, maxTokenExpires.toMillis()); return Mono - .defer(() -> { - Mono doSign = Mono.defer(() -> { - Map map = new HashMap<>(); - map.put("token", token); - map.put("type", type); - map.put("userId", userId); - map.put("maxInactiveInterval", expires); - map.put("state", TokenState.normal.getValue()); - map.put("signInTime", System.currentTimeMillis()); - map.put("lastRequestTime", System.currentTimeMillis()); - cacheBuilder.accept(map); - String key = getTokenRedisKey(token); - return userTokenStore - .putAll(key, map) - .then(Mono.defer(() -> { - if (expires > 0) { - return operations.expire(key, Duration.ofMillis(expires)); - } - return Mono.empty(); - })) - .then(userTokenMapping.add(getUserRedisKey(userId), token)) - .thenReturn(SimpleUserToken.of(map)); - }); - if(ignoreAllopatricLoginMode){ - return doSign; - } - AllopatricLoginMode mode = allopatricLoginModes.getOrDefault(type, allopatricLoginMode); - if (mode == AllopatricLoginMode.deny) { - return userIsLoggedIn(userId) - .flatMap(r -> { - if (r) { - return Mono.error(new AccessDenyException("error.logged_in_elsewhere", TokenState.deny.getValue())); - } - return doSign; - }); - - } else if (mode == AllopatricLoginMode.offlineOther) { - return getByUserId(userId) - .flatMap(userToken -> { - if (type.equals(userToken.getType())) { - return this.changeTokenState(userToken.getToken(), TokenState.offline); - } - return Mono.empty(); - }) - .then(doSign); - } - + .defer(() -> { + Mono doSign = Mono.defer(() -> { + Map map = new HashMap<>(); + map.put("token", token); + map.put("type", type); + map.put("userId", userId); + map.put("maxInactiveInterval", expires); + map.put("state", TokenState.normal.getValue()); + map.put("signInTime", System.currentTimeMillis()); + map.put("lastRequestTime", System.currentTimeMillis()); + cacheBuilder.accept(map); + String key = getTokenRedisKey(token); + return userTokenStore + .putAll(key, map) + .then(Mono.defer(() -> { + if (expires > 0) { + return operations.expire(key, Duration.ofMillis(expires)); + } + return Mono.empty(); + })) + .then(userTokenMapping.add(getUserRedisKey(userId), token)) + .thenReturn(SimpleUserToken.of(map)); + }); + if(ignoreAllopatricLoginMode){ return doSign; - }) - .flatMap(this::onUserTokenCreated); + } + AllopatricLoginMode mode = allopatricLoginModes.getOrDefault(type, allopatricLoginMode); + if (mode == AllopatricLoginMode.deny) { + return userIsLoggedIn(userId) + .flatMap(r -> { + if (r) { + return Mono.error(new AccessDenyException("error.logged_in_elsewhere", TokenState.deny.getValue())); + } + return doSign; + }); + + } else if (mode == AllopatricLoginMode.offlineOther) { + return getByUserId(userId) + .flatMap(userToken -> { + if (type.equals(userToken.getType())) { + return this.changeTokenState(userToken.getToken(), TokenState.offline); + } + return Mono.empty(); + }) + .then(doSign); + } + + return doSign; + }) + .flatMap(this::onUserTokenCreated); } @Override @@ -311,32 +311,32 @@ public class RedisUserTokenManager implements UserTokenManager { return Mono.empty(); } return getByToken(token) - .flatMap(userToken -> { - if (userToken.getMaxInactiveInterval() > 0) { - touchSink.next(userToken); - } - return Mono.empty(); - }); + .flatMap(userToken -> { + if (userToken.getMaxInactiveInterval() > 0) { + touchSink.next(userToken); + } + return Mono.empty(); + }); } @Override public Mono checkExpiredToken() { return operations - .scan(ScanOptions.scanOptions().match("*user-token-user:*").build()) + .scan(ScanOptions.scanOptions().match("*user-token-user:*").build()) + .map(String::valueOf) + .flatMap(key -> userTokenMapping + .members(key) .map(String::valueOf) - .flatMap(key -> userTokenMapping - .members(key) - .map(String::valueOf) - .flatMap(token -> operations - .hasKey(getTokenRedisKey(token)) - .flatMap(exists -> { - if (!exists) { - return userTokenMapping.remove(key, token); - } - return Mono.empty(); - }))) - .then(); + .flatMap(token -> operations + .hasKey(getTokenRedisKey(token)) + .flatMap(exists -> { + if (!exists) { + return userTokenMapping.remove(key, token); + } + return Mono.empty(); + }))) + .then(); } private Mono notifyTokenRemoved(String token) { @@ -368,7 +368,7 @@ public class RedisUserTokenManager implements UserTokenManager { localCache.put(token.getToken(), token); if (eventPublisher == null) { return notifyTokenRemoved(token.getToken()) - .thenReturn(token); + .thenReturn(token); } return new UserTokenCreatedEvent(token) .publish(eventPublisher) diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java index 32a6cf48b..de642d936 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/aop/AopAuthorizingController.java @@ -70,7 +70,10 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor .invokeReactive( Authentication .currentReactive() - .switchIfEmpty(Mono.error(UnAuthorizedException.NoStackTrace::new)) + .switchIfEmpty( + context.getDefinition().allowAnonymous() + ? Mono.empty() + : Mono.error(UnAuthorizedException.NoStackTrace::new)) .flatMap(auth -> { context.setAuthentication(auth); //响应式不再支持数据权限控制 @@ -124,7 +127,7 @@ public class AopAuthorizingController extends StaticMethodMatcherPointcutAdvisor Class returnType = methodInvocation.getMethod().getReturnType(); //handle reactive method if (Publisher.class.isAssignableFrom(returnType)) { - return handleReactive0(definition, holder, context, () -> invokeReactive(methodInvocation)); + return handleReactive0(definition, holder, context, () -> invokeReactive(methodInvocation)); } Authentication authentication = Authentication diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/define/DefaultBasicAuthorizeDefinition.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/define/DefaultBasicAuthorizeDefinition.java index fafb7a34e..0fe9fceb0 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/define/DefaultBasicAuthorizeDefinition.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/define/DefaultBasicAuthorizeDefinition.java @@ -43,18 +43,20 @@ public class DefaultBasicAuthorizeDefinition implements AopAuthorizeDefinition { private Phased phased = Phased.before; + private boolean allowAnonymous = false; + @Override public boolean isEmpty() { return false; } private static final Set> types = new HashSet<>(Arrays.asList( - Authorize.class, - DataAccess.class, - Dimension.class, - Resource.class, - ResourceAction.class, - DataAccessType.class + Authorize.class, + DataAccess.class, + Dimension.class, + Resource.class, + ResourceAction.class, + DataAccessType.class )); public static AopAuthorizeDefinition from(Class targetClass, Method method) { @@ -76,6 +78,9 @@ public class DefaultBasicAuthorizeDefinition implements AopAuthorizeDefinition { for (Dimension dimension : ann.dimension()) { putAnnotation(dimension); } + if (ann.anonymous()) { + allowAnonymous = true; + } } public void putAnnotation(Dimension ann) {