mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-06-03 11:24:34 +08:00
Merge branch 'oauth2-support' into 4.0.x
# Conflicts: # hsweb-authorization/hsweb-authorization-api/pom.xml # hsweb-authorization/hsweb-authorization-basic/pom.xml # hsweb-authorization/pom.xml # hsweb-commons/hsweb-commons-api/pom.xml # hsweb-commons/hsweb-commons-crud/pom.xml # hsweb-commons/pom.xml # hsweb-concurrent/hsweb-concurrent-cache/pom.xml # hsweb-concurrent/pom.xml # hsweb-core/pom.xml # hsweb-datasource/hsweb-datasource-api/pom.xml # hsweb-datasource/hsweb-datasource-jta/pom.xml # hsweb-datasource/hsweb-datasource-web/pom.xml # hsweb-datasource/pom.xml # hsweb-logging/hsweb-access-logging-aop/pom.xml # hsweb-logging/hsweb-access-logging-api/pom.xml # hsweb-logging/pom.xml # hsweb-starter/pom.xml # hsweb-system/hsweb-system-authorization/hsweb-system-authorization-api/pom.xml # hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/pom.xml # hsweb-system/hsweb-system-authorization/pom.xml # hsweb-system/hsweb-system-dictionary/pom.xml # hsweb-system/hsweb-system-file/pom.xml # hsweb-system/pom.xml # pom.xml
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -207,4 +209,13 @@ public interface Authentication extends Serializable {
|
||||
*/
|
||||
Authentication merge(Authentication source);
|
||||
|
||||
/**
|
||||
* copy为新的权限信息
|
||||
*
|
||||
* @param permissionFilter 权限过滤
|
||||
* @param dimension 维度过滤
|
||||
* @return 新的权限信息
|
||||
*/
|
||||
Authentication copy(BiPredicate<Permission, String> permissionFilter,
|
||||
Predicate<Dimension> dimension);
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ public interface Permission extends Serializable {
|
||||
* @see FieldFilterDataAccessConfig#getFields()
|
||||
*/
|
||||
default Optional<FieldFilterDataAccessConfig> findFieldFilter(String action) {
|
||||
return findDataAccess(conf -> FieldFilterDataAccessConfig.class.isInstance(conf) && conf.getAction().equals(action));
|
||||
return findDataAccess(conf -> conf instanceof FieldFilterDataAccessConfig && conf.getAction().equals(action));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +164,7 @@ public interface Permission extends Serializable {
|
||||
*/
|
||||
default Set<String> findDenyFields(String action) {
|
||||
return findFieldFilter(action)
|
||||
.filter(conf -> DENY_FIELDS.equals(conf.getType()))
|
||||
.filter(conf -> DENY_FIELDS.equals(conf.getType().getId()))
|
||||
.map(FieldFilterDataAccessConfig::getFields)
|
||||
.orElseGet(Collections::emptySet);
|
||||
}
|
||||
@@ -210,6 +210,8 @@ public interface Permission extends Serializable {
|
||||
|
||||
Permission copy();
|
||||
|
||||
Permission copy(Predicate<String> actionFilter,Predicate<DataAccessConfig> dataAccessFilter);
|
||||
|
||||
/**
|
||||
* 数据权限查找判断逻辑接口
|
||||
*
|
||||
|
||||
@@ -6,10 +6,7 @@ import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFacto
|
||||
import org.hswebframework.web.authorization.simple.builder.DataAccessConfigConverter;
|
||||
import org.hswebframework.web.authorization.simple.builder.SimpleAuthenticationBuilderFactory;
|
||||
import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfigBuilderFactory;
|
||||
import org.hswebframework.web.authorization.token.DefaultUserTokenManager;
|
||||
import org.hswebframework.web.authorization.token.UserTokenAuthenticationSupplier;
|
||||
import org.hswebframework.web.authorization.token.UserTokenReactiveAuthenticationSupplier;
|
||||
import org.hswebframework.web.authorization.token.UserTokenManager;
|
||||
import org.hswebframework.web.authorization.token.*;
|
||||
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
|
||||
import org.hswebframework.web.authorization.twofactor.defaults.DefaultTwoFactorValidatorManager;
|
||||
import org.hswebframework.web.convert.CustomMessageConverter;
|
||||
|
||||
@@ -23,7 +23,9 @@ import org.hswebframework.web.authorization.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@@ -40,9 +42,10 @@ public class SimpleAuthentication implements Authentication {
|
||||
|
||||
private Map<String, Serializable> attributes = new HashMap<>();
|
||||
|
||||
public static Authentication of(){
|
||||
public static Authentication of() {
|
||||
return new SimpleAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Serializable> Optional<T> getAttribute(String name) {
|
||||
@@ -77,4 +80,19 @@ public class SimpleAuthentication implements Authentication {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication copy(BiPredicate<Permission, String> permissionFilter,
|
||||
Predicate<Dimension> dimension) {
|
||||
SimpleAuthentication authentication = new SimpleAuthentication();
|
||||
authentication.setUser(user);
|
||||
authentication.setDimensions(dimensions.stream().filter(dimension).collect(Collectors.toList()));
|
||||
authentication.setPermissions(permissions
|
||||
.stream()
|
||||
.map(permission -> permission.copy(action -> permissionFilter.test(permission, action), conf -> true))
|
||||
.filter(per -> !per.getActions().isEmpty())
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
return authentication;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import org.hswebframework.web.authorization.Permission;
|
||||
import org.hswebframework.web.authorization.access.DataAccessConfig;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
@@ -42,16 +44,22 @@ public class SimplePermission implements Permission {
|
||||
return dataAccesses;
|
||||
}
|
||||
|
||||
public Permission copy() {
|
||||
@Override
|
||||
public Permission copy(Predicate<String> actionFilter,
|
||||
Predicate<DataAccessConfig> dataAccessFilter) {
|
||||
SimplePermission permission = new SimplePermission();
|
||||
|
||||
permission.setId(id);
|
||||
permission.setName(name);
|
||||
permission.setActions(new HashSet<>(getActions()));
|
||||
permission.setDataAccesses(new HashSet<>(getDataAccesses()));
|
||||
permission.setActions(getActions().stream().filter(actionFilter).collect(Collectors.toSet()));
|
||||
permission.setDataAccesses(getDataAccesses().stream().filter(dataAccessFilter).collect(Collectors.toSet()));
|
||||
if (options != null) {
|
||||
permission.setOptions(new HashMap<>(options));
|
||||
}
|
||||
return permission;
|
||||
}
|
||||
|
||||
public Permission copy() {
|
||||
return copy(action -> true, conf -> true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,4 +15,8 @@ public interface ParsedToken {
|
||||
* @return 令牌类型
|
||||
*/
|
||||
String getType();
|
||||
|
||||
static ParsedToken of(String type, String token) {
|
||||
return SimpleParsedToken.of(type, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.hswebframework.web.authorization.token;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier;
|
||||
import org.hswebframework.web.context.ContextKey;
|
||||
import org.hswebframework.web.context.ContextUtils;
|
||||
import org.hswebframework.web.logger.ReactiveLogger;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class ReactiveTokenAuthenticationSupplier implements ReactiveAuthenticationSupplier {
|
||||
|
||||
private final TokenAuthenticationManager tokenManager;
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> get(String userId) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> get() {
|
||||
return ContextUtils.reactiveContext()
|
||||
.flatMap(context ->
|
||||
context.get(ContextKey.of(ParsedToken.class))
|
||||
.map(t -> tokenManager.getByToken(t.getToken()))
|
||||
.orElseGet(Mono::empty))
|
||||
.flatMap(auth -> ReactiveLogger.mdc("userId", auth.getUser().getId())
|
||||
.then(ReactiveLogger.mdc("username", auth.getUser().getName()))
|
||||
.thenReturn(auth));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.hswebframework.web.authorization.token;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor(staticName = "of")
|
||||
public class SimpleParsedToken implements ParsedToken{
|
||||
|
||||
private String type;
|
||||
|
||||
private String token;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.hswebframework.web.authorization.token;
|
||||
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* token 权限管理器,根据token来进行权限关联.
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 4.0.7
|
||||
*/
|
||||
public interface TokenAuthenticationManager {
|
||||
|
||||
/**
|
||||
* 根据token获取认证信息
|
||||
*
|
||||
* @param token token
|
||||
* @return 认证信息
|
||||
*/
|
||||
Mono<Authentication> getByToken(String token);
|
||||
|
||||
/**
|
||||
* 设置token认证信息
|
||||
*
|
||||
* @param token token
|
||||
* @param auth 认证信息
|
||||
* @param ttl 有效期
|
||||
* @return void
|
||||
*/
|
||||
Mono<Void> putAuthentication(String token, Authentication auth, Duration ttl);
|
||||
|
||||
/**
|
||||
* 删除token
|
||||
* @param token token
|
||||
* @return void
|
||||
*/
|
||||
Mono<Void> removeToken(String token);
|
||||
}
|
||||
@@ -19,13 +19,14 @@ import java.util.Map;
|
||||
*/
|
||||
public class UserTokenReactiveAuthenticationSupplier implements ReactiveAuthenticationSupplier {
|
||||
|
||||
private ReactiveAuthenticationManager defaultAuthenticationManager;
|
||||
private final ReactiveAuthenticationManager defaultAuthenticationManager;
|
||||
|
||||
private UserTokenManager userTokenManager;
|
||||
private final UserTokenManager userTokenManager;
|
||||
|
||||
private Map<String, ThirdPartReactiveAuthenticationManager> thirdPartAuthenticationManager = new HashMap<>();
|
||||
private final Map<String, ThirdPartReactiveAuthenticationManager> thirdPartAuthenticationManager = new HashMap<>();
|
||||
|
||||
public UserTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager, ReactiveAuthenticationManager defaultAuthenticationManager) {
|
||||
public UserTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager,
|
||||
ReactiveAuthenticationManager defaultAuthenticationManager) {
|
||||
this.defaultAuthenticationManager = defaultAuthenticationManager;
|
||||
this.userTokenManager = userTokenManager;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.hswebframework.web.authorization.token.UserToken;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
public class UserTokenChangedEvent extends ApplicationEvent implements AuthorizationEvent {
|
||||
private UserToken before, after;
|
||||
private final UserToken before, after;
|
||||
|
||||
public UserTokenChangedEvent(UserToken before, UserToken after) {
|
||||
super(after);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.hswebframework.web.authorization.token.redis;
|
||||
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.authorization.token.TokenAuthenticationManager;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public class RedisTokenAuthenticationManager implements TokenAuthenticationManager {
|
||||
|
||||
private final ReactiveRedisOperations<String, Authentication> operations;
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public RedisTokenAuthenticationManager(ReactiveRedisConnectionFactory connectionFactory) {
|
||||
this(new ReactiveRedisTemplate<>(
|
||||
connectionFactory, RedisSerializationContext.<String, Authentication>newSerializationContext()
|
||||
.key(RedisSerializer.string())
|
||||
.value((RedisSerializer) RedisSerializer.java())
|
||||
.hashKey(RedisSerializer.string())
|
||||
.hashValue(RedisSerializer.java())
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
public RedisTokenAuthenticationManager(ReactiveRedisOperations<String, Authentication> operations) {
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> getByToken(String token) {
|
||||
return operations
|
||||
.opsForValue()
|
||||
.get("token-auth:" + token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> removeToken(String token) {
|
||||
return operations
|
||||
.delete(token)
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> putAuthentication(String token, Authentication auth, Duration ttl) {
|
||||
return ttl.isNegative()
|
||||
? operations
|
||||
.opsForValue()
|
||||
.set("token-auth:" + token, auth)
|
||||
.then()
|
||||
: operations
|
||||
.opsForValue()
|
||||
.set("token-auth:" + token, auth, ttl)
|
||||
.then()
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -2,21 +2,24 @@ package org.hswebframework.web.authorization.token.redis;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.hswebframework.web.authorization.exception.AccessDenyException;
|
||||
import org.hswebframework.web.authorization.token.AllopatricLoginMode;
|
||||
import org.hswebframework.web.authorization.token.TokenState;
|
||||
import org.hswebframework.web.authorization.token.UserToken;
|
||||
import org.hswebframework.web.authorization.token.UserTokenManager;
|
||||
import org.springframework.data.redis.core.ReactiveHashOperations;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.data.redis.core.ReactiveSetOperations;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.hswebframework.web.authorization.token.event.UserTokenChangedEvent;
|
||||
import org.hswebframework.web.authorization.token.event.UserTokenCreatedEvent;
|
||||
import org.hswebframework.web.authorization.token.event.UserTokenRemovedEvent;
|
||||
import org.hswebframework.web.bean.FastBeanCopier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.*;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -35,6 +38,18 @@ public class RedisUserTokenManager implements UserTokenManager {
|
||||
this.userTokenMapping = operations.opsForSet();
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public RedisUserTokenManager(ReactiveRedisConnectionFactory connectionFactory) {
|
||||
this(new ReactiveRedisTemplate<>(connectionFactory,
|
||||
RedisSerializationContext.newSerializationContext()
|
||||
.key((RedisSerializer) RedisSerializer.string())
|
||||
.value(RedisSerializer.java())
|
||||
.hashKey(RedisSerializer.string())
|
||||
.hashValue(RedisSerializer.java())
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Map<String, AllopatricLoginMode> allopatricLoginModes = new HashMap<>();
|
||||
@@ -44,6 +59,9 @@ public class RedisUserTokenManager implements UserTokenManager {
|
||||
//异地登录模式,默认允许异地登录
|
||||
private AllopatricLoginMode allopatricLoginMode = AllopatricLoginMode.allow;
|
||||
|
||||
@Setter
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
private String getTokenRedisKey(String key) {
|
||||
return "user-token:".concat(key);
|
||||
}
|
||||
@@ -114,20 +132,24 @@ public class RedisUserTokenManager implements UserTokenManager {
|
||||
public Mono<Void> signOutByUserId(String userId) {
|
||||
String key = getUserRedisKey(userId);
|
||||
return getByUserId(key)
|
||||
.map(UserToken::getToken)
|
||||
.map(this::getTokenRedisKey)
|
||||
.concatWithValues(key)
|
||||
.as(operations::delete)
|
||||
.flatMap(userToken -> operations
|
||||
.delete(getTokenRedisKey(userToken.getToken()))
|
||||
.then(onTokenRemoved(userToken)))
|
||||
.then(operations.delete(key))
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> signOutByToken(String token) {
|
||||
//delete token
|
||||
// srem user token
|
||||
//srem user token
|
||||
return getByToken(token)
|
||||
.flatMap(t -> operations.delete(getTokenRedisKey(t.getToken()))
|
||||
.then(userTokenMapping.remove(getUserRedisKey(t.getToken()),token))).then();
|
||||
.flatMap(t -> operations
|
||||
.delete(getTokenRedisKey(t.getToken()))
|
||||
.then(userTokenMapping.remove(getUserRedisKey(t.getToken()), token))
|
||||
.then(onTokenRemoved(t))
|
||||
)
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,60 +162,68 @@ public class RedisUserTokenManager implements UserTokenManager {
|
||||
|
||||
@Override
|
||||
public Mono<Void> changeTokenState(String token, TokenState state) {
|
||||
return userTokenStore
|
||||
.put(getTokenRedisKey(token), "state", state.getValue())
|
||||
.then();
|
||||
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UserToken> signIn(String token, String type, String userId, long maxInactiveInterval) {
|
||||
return Mono.defer(() -> {
|
||||
Mono<UserToken> doSign = Mono.defer(() -> {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("token", token);
|
||||
map.put("type", type);
|
||||
map.put("userId", userId);
|
||||
map.put("maxInactiveInterval", maxInactiveInterval);
|
||||
map.put("state", TokenState.normal.getValue());
|
||||
map.put("signInTime", System.currentTimeMillis());
|
||||
map.put("lastRequestTime", System.currentTimeMillis());
|
||||
return Mono
|
||||
.defer(() -> {
|
||||
Mono<UserToken> doSign = Mono.defer(() -> {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("token", token);
|
||||
map.put("type", type);
|
||||
map.put("userId", userId);
|
||||
map.put("maxInactiveInterval", maxInactiveInterval);
|
||||
map.put("state", TokenState.normal.getValue());
|
||||
map.put("signInTime", System.currentTimeMillis());
|
||||
map.put("lastRequestTime", System.currentTimeMillis());
|
||||
|
||||
String key = getTokenRedisKey(token);
|
||||
return userTokenStore
|
||||
.putAll(key, map)
|
||||
.then(Mono.defer(() -> {
|
||||
if (maxInactiveInterval > 0) {
|
||||
return operations.expire(key, Duration.ofMillis(maxInactiveInterval));
|
||||
}
|
||||
return Mono.empty();
|
||||
}))
|
||||
.then(userTokenMapping.add(getUserRedisKey(userId), token))
|
||||
.thenReturn(SimpleUserToken.of(map));
|
||||
});
|
||||
String key = getTokenRedisKey(token);
|
||||
return userTokenStore
|
||||
.putAll(key, map)
|
||||
.then(Mono.defer(() -> {
|
||||
if (maxInactiveInterval > 0) {
|
||||
return operations.expire(key, Duration.ofMillis(maxInactiveInterval));
|
||||
}
|
||||
return Mono.empty();
|
||||
}))
|
||||
.then(userTokenMapping.add(getUserRedisKey(userId), token))
|
||||
.thenReturn(SimpleUserToken.of(map));
|
||||
});
|
||||
|
||||
AllopatricLoginMode mode = allopatricLoginModes.getOrDefault(type, allopatricLoginMode);
|
||||
if (mode == AllopatricLoginMode.deny) {
|
||||
return userIsLoggedIn(userId)
|
||||
.flatMap(r -> {
|
||||
if (r) {
|
||||
return Mono.error(new AccessDenyException("已在其他地方登录", TokenState.deny.getValue(), null));
|
||||
}
|
||||
return doSign;
|
||||
});
|
||||
AllopatricLoginMode mode = allopatricLoginModes.getOrDefault(type, allopatricLoginMode);
|
||||
if (mode == AllopatricLoginMode.deny) {
|
||||
return userIsLoggedIn(userId)
|
||||
.flatMap(r -> {
|
||||
if (r) {
|
||||
return Mono.error(new AccessDenyException("已在其他地方登录", TokenState.deny.getValue(), null));
|
||||
}
|
||||
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);
|
||||
}
|
||||
} 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;
|
||||
});
|
||||
return doSign;
|
||||
})
|
||||
.flatMap(this::onUserTokenCreated);
|
||||
}
|
||||
|
||||
|
||||
@@ -213,9 +243,8 @@ public class RedisUserTokenManager implements UserTokenManager {
|
||||
@Override
|
||||
public Mono<Void> checkExpiredToken() {
|
||||
|
||||
return operations.scan(ScanOptions
|
||||
.scanOptions()
|
||||
.match("user-token-user:*").build())
|
||||
return operations
|
||||
.scan(ScanOptions.scanOptions().match("user-token-user:*").build())
|
||||
.map(String::valueOf)
|
||||
.flatMap(key -> userTokenMapping.members(key)
|
||||
.map(String::valueOf)
|
||||
@@ -228,4 +257,28 @@ public class RedisUserTokenManager implements UserTokenManager {
|
||||
})))
|
||||
.then();
|
||||
}
|
||||
|
||||
private Mono<Void> onTokenRemoved(UserToken token) {
|
||||
if (eventPublisher == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Mono.fromRunnable(() -> eventPublisher.publishEvent(new UserTokenRemovedEvent(token)));
|
||||
}
|
||||
|
||||
private Mono<Void> onTokenChanged(UserToken old, UserToken newToken) {
|
||||
if (eventPublisher == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Mono.fromRunnable(() -> eventPublisher.publishEvent(new UserTokenChangedEvent(old, newToken)));
|
||||
}
|
||||
|
||||
private Mono<UserToken> onUserTokenCreated(UserToken token) {
|
||||
if (eventPublisher == null) {
|
||||
return Mono.just(token);
|
||||
}
|
||||
return Mono
|
||||
.fromRunnable(() -> eventPublisher.publishEvent(new UserTokenCreatedEvent(token)))
|
||||
.thenReturn(token);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -17,11 +17,8 @@
|
||||
|
||||
package org.hswebframework.web.authorization.basic.web;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.SneakyThrows;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
@@ -33,12 +30,10 @@ import org.hswebframework.web.authorization.events.AuthorizationFailedEvent;
|
||||
import org.hswebframework.web.authorization.events.AuthorizationSuccessEvent;
|
||||
import org.hswebframework.web.authorization.exception.AuthenticationException;
|
||||
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
|
||||
import org.hswebframework.web.authorization.simple.CompositeReactiveAuthenticationManager;
|
||||
import org.hswebframework.web.authorization.simple.PlainTextUsernamePasswordAuthenticationRequest;
|
||||
import org.hswebframework.web.logging.AccessLogger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -71,7 +66,6 @@ public class AuthorizationController {
|
||||
}
|
||||
|
||||
@PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@ApiOperation("用户名密码登录,json方式")
|
||||
@Authorize(ignore = true)
|
||||
@AccessLogger(ignore = true)
|
||||
@Operation(summary = "登录",description = "必要参数:username,password.根据配置不同,其他参数也不同,如:验证码等.")
|
||||
|
||||
52
hsweb-authorization/hsweb-authorization-oauth2/pom.xml
Normal file
52
hsweb-authorization/hsweb-authorization-oauth2/pom.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>hsweb-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>hsweb-authorization-oauth2</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-authorization-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webflux</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-redis</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-authorization-basic</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2020 http://www.hswebframework.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package org.hswebframework.web.oauth2;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public enum ErrorType {
|
||||
ILLEGAL_CODE(1001), //错误的授权码
|
||||
ILLEGAL_ACCESS_TOKEN(1002), //错误的access_token
|
||||
ILLEGAL_CLIENT_ID(1003),//客户端信息错误
|
||||
ILLEGAL_CLIENT_SECRET(1004),//客户端密钥错误
|
||||
ILLEGAL_GRANT_TYPE(1005), //错误的授权方式
|
||||
ILLEGAL_RESPONSE_TYPE(1006),//response_type 错误
|
||||
ILLEGAL_AUTHORIZATION(1007),//Authorization 错误
|
||||
ILLEGAL_REFRESH_TOKEN(1008),//refresh_token 错误
|
||||
ILLEGAL_REDIRECT_URI(1009), //redirect_url 错误
|
||||
ILLEGAL_SCOPE(1010), //scope 错误
|
||||
ILLEGAL_USERNAME(1011), //username 错误
|
||||
ILLEGAL_PASSWORD(1012), //password 错误
|
||||
|
||||
SCOPE_OUT_OF_RANGE(2010), //scope超出范围
|
||||
|
||||
UNAUTHORIZED_CLIENT(4010), //无权限
|
||||
EXPIRED_TOKEN(4011), //TOKEN过期
|
||||
INVALID_TOKEN(4012), //TOKEN已失效
|
||||
UNSUPPORTED_GRANT_TYPE(4013), //不支持的认证类型
|
||||
UNSUPPORTED_RESPONSE_TYPE(4014), //不支持的响应类型
|
||||
|
||||
EXPIRED_CODE(4015), //AUTHORIZATION_CODE过期
|
||||
EXPIRED_REFRESH_TOKEN(4020), //REFRESH_TOKEN过期
|
||||
|
||||
CLIENT_DISABLED(4016),//客户端已被禁用
|
||||
|
||||
CLIENT_NOT_EXIST(4040),//客户端不存在
|
||||
|
||||
USER_NOT_EXIST(4041),//客户端不存在
|
||||
|
||||
STATE_ERROR(4042), //stat错误
|
||||
|
||||
ACCESS_DENIED(503), //访问被拒绝
|
||||
|
||||
OTHER(5001), //其他错误 ;
|
||||
|
||||
PARSE_RESPONSE_ERROR(5002),//解析返回结果错误
|
||||
|
||||
SERVICE_ERROR(5003); //服务器返回错误信息
|
||||
|
||||
|
||||
private final String message;
|
||||
private final int code;
|
||||
static final Map<Integer, ErrorType> codeMapping = Arrays.stream(ErrorType.values())
|
||||
.collect(Collectors.toMap(ErrorType::code, type -> type));
|
||||
|
||||
ErrorType(int code) {
|
||||
this.code = code;
|
||||
message = this.name().toLowerCase();
|
||||
}
|
||||
|
||||
ErrorType(int code, String message) {
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String message() {
|
||||
if (message == null) {
|
||||
return this.name();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public <T> T throwThis(Function<ErrorType, ? extends RuntimeException> errorTypeFunction) {
|
||||
throw errorTypeFunction.apply(this);
|
||||
}
|
||||
|
||||
public <T> T throwThis(BiFunction<ErrorType, String, ? extends RuntimeException> errorTypeFunction, String message) {
|
||||
throw errorTypeFunction.apply(this, message);
|
||||
}
|
||||
|
||||
public static Optional<ErrorType> fromCode(int code) {
|
||||
return Optional.ofNullable(codeMapping.get(code));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2020 http://www.hswebframework.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package org.hswebframework.web.oauth2;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhouhao
|
||||
*/
|
||||
public interface GrantType {
|
||||
String authorization_code = "authorization_code";
|
||||
String implicit = "implicit";
|
||||
@SuppressWarnings("all")
|
||||
String password = "password";
|
||||
String client_credentials = "client_credentials";
|
||||
String refresh_token = "refresh_token";
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2020 http://www.hswebframework.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package org.hswebframework.web.oauth2;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
*/
|
||||
public interface OAuth2Constants {
|
||||
String access_token = "access_token";
|
||||
String refresh_token = "refresh_token";
|
||||
String grant_type = "grant_type";
|
||||
String scope = "scope";
|
||||
String client_id = "client_id";
|
||||
String client_secret = "client_secret";
|
||||
String authorization = "Authorization";
|
||||
String redirect_uri = "redirect_uri";
|
||||
String response_type = "response_type";
|
||||
String state = "state";
|
||||
String code = "code";
|
||||
String username = "username";
|
||||
|
||||
@SuppressWarnings("all")
|
||||
String password = "password";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.hswebframework.web.oauth2;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.web.exception.BusinessException;
|
||||
|
||||
@Getter
|
||||
public class OAuth2Exception extends BusinessException {
|
||||
private final ErrorType type;
|
||||
|
||||
public OAuth2Exception(ErrorType type) {
|
||||
super(type.message(), type.name(), type.code());
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public OAuth2Exception(String message, Throwable cause, ErrorType type) {
|
||||
super(message, cause);
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2020 http://www.hswebframework.org
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package org.hswebframework.web.oauth2;
|
||||
|
||||
/**
|
||||
* TODO 完成注释
|
||||
*
|
||||
* @author zhouhao
|
||||
*/
|
||||
public interface ResponseType {
|
||||
String code = "code";
|
||||
String token = "token";
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.*;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public class AccessToken extends OAuth2Response {
|
||||
|
||||
private static final long serialVersionUID = -6849794470754667710L;
|
||||
|
||||
@Schema(name="access_token")
|
||||
@JsonProperty("access_token")
|
||||
private String accessToken;
|
||||
|
||||
@Schema(name="refresh_token")
|
||||
@JsonProperty("refresh_token")
|
||||
private String refreshToken;
|
||||
|
||||
@Schema(name="expires_in")
|
||||
@JsonProperty("expires_in")
|
||||
private int expiresIn;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface AccessTokenManager {
|
||||
|
||||
Mono<Authentication> getAuthenticationByToken(String accessToken);
|
||||
|
||||
Mono<AccessToken> createAccessToken(String clientId,
|
||||
Authentication authentication,
|
||||
boolean singleton);
|
||||
|
||||
Mono<AccessToken> refreshAccessToken(String clientId, String refreshToken);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
public interface ClientCredentialGranter extends OAuth2Granter {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.oauth2.ErrorType;
|
||||
import org.hswebframework.web.oauth2.OAuth2Exception;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class OAuth2Client {
|
||||
|
||||
@NotBlank
|
||||
private String clientId;
|
||||
|
||||
@NotBlank
|
||||
private String clientSecret;
|
||||
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
@NotBlank
|
||||
private String redirectUrl;
|
||||
|
||||
//client 所属用户
|
||||
private String userId;
|
||||
|
||||
public void validateRedirectUri(String redirectUri) {
|
||||
if (StringUtils.isEmpty(redirectUri) || (!redirectUri.startsWith(this.redirectUrl))) {
|
||||
throw new OAuth2Exception(ErrorType.ILLEGAL_REDIRECT_URI);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface OAuth2ClientManager {
|
||||
|
||||
Mono<OAuth2Client> getClient(String clientId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
|
||||
import org.hswebframework.web.oauth2.server.code.AuthorizationCodeGranter;
|
||||
|
||||
public interface OAuth2GrantService {
|
||||
|
||||
AuthorizationCodeGranter authorizationCode();
|
||||
|
||||
ClientCredentialGranter clientCredential();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
public interface OAuth2Granter {
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class OAuth2Request {
|
||||
|
||||
private Map<String, Object> parameters;
|
||||
|
||||
|
||||
public Optional<Object> getParameter(String key) {
|
||||
return Optional.ofNullable(parameters)
|
||||
.map(params -> params.get(key));
|
||||
}
|
||||
|
||||
public OAuth2Request with(String parameter, Object key) {
|
||||
if (parameters == null) {
|
||||
parameters = new HashMap<>();
|
||||
}
|
||||
parameters.put(parameter, key);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class OAuth2Response implements Serializable {
|
||||
@Hidden
|
||||
private Map<String,Object> parameters;
|
||||
|
||||
public OAuth2Response with(String parameter, Object key) {
|
||||
if (parameters == null) {
|
||||
parameters = new HashMap<>();
|
||||
}
|
||||
parameters.put(parameter, key);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import org.hswebframework.web.authorization.ReactiveAuthenticationHolder;
|
||||
import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenParser;
|
||||
import org.hswebframework.web.oauth2.server.auth.ReactiveOAuth2AccessTokenParser;
|
||||
import org.hswebframework.web.oauth2.server.code.AuthorizationCodeGranter;
|
||||
import org.hswebframework.web.oauth2.server.code.DefaultAuthorizationCodeGranter;
|
||||
import org.hswebframework.web.oauth2.server.impl.CompositeOAuth2GrantService;
|
||||
import org.hswebframework.web.oauth2.server.impl.RedisAccessTokenManager;
|
||||
import org.hswebframework.web.oauth2.server.web.OAuth2AuthorizeController;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class OAuth2ServerAutoConfiguration {
|
||||
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ReactiveUserTokenParser.class)
|
||||
static class ReactiveOAuth2AccessTokenParserConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(AccessTokenManager.class)
|
||||
public ReactiveOAuth2AccessTokenParser reactiveOAuth2AccessTokenParser(AccessTokenManager accessTokenManager) {
|
||||
ReactiveOAuth2AccessTokenParser parser = new ReactiveOAuth2AccessTokenParser(accessTokenManager);
|
||||
ReactiveAuthenticationHolder.addSupplier(parser);
|
||||
return parser;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
static class ReactiveOAuth2ServerAutoConfiguration {
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AccessTokenManager accessTokenManager(ReactiveRedisConnectionFactory redisConnectionFactory) {
|
||||
return new RedisAccessTokenManager(redisConnectionFactory);
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AuthorizationCodeGranter authorizationCodeGranter(AccessTokenManager tokenManager,
|
||||
ReactiveRedisConnectionFactory redisConnectionFactory) {
|
||||
return new DefaultAuthorizationCodeGranter(tokenManager, redisConnectionFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OAuth2GrantService oAuth2GrantService(ObjectProvider<AuthorizationCodeGranter> codeProvider,
|
||||
ObjectProvider<ClientCredentialGranter> credentialProvider) {
|
||||
CompositeOAuth2GrantService grantService = new CompositeOAuth2GrantService();
|
||||
grantService.setAuthorizationCodeGranter(codeProvider.getIfAvailable());
|
||||
grantService.setClientCredentialGranter(credentialProvider.getIfAvailable());
|
||||
|
||||
return grantService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(OAuth2ClientManager.class)
|
||||
public OAuth2AuthorizeController oAuth2AuthorizeController(OAuth2GrantService grantService,
|
||||
OAuth2ClientManager clientManager) {
|
||||
return new OAuth2AuthorizeController(grantService, clientManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ScopePredicate extends BiPredicate<String, String[]> {
|
||||
|
||||
boolean test(String permission, String... actions);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.hswebframework.web.oauth2.server.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier;
|
||||
import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenParser;
|
||||
import org.hswebframework.web.authorization.token.ParsedToken;
|
||||
import org.hswebframework.web.context.ContextKey;
|
||||
import org.hswebframework.web.context.ContextUtils;
|
||||
import org.hswebframework.web.logger.ReactiveLogger;
|
||||
import org.hswebframework.web.oauth2.server.AccessTokenManager;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class ReactiveOAuth2AccessTokenParser implements ReactiveUserTokenParser, ReactiveAuthenticationSupplier {
|
||||
|
||||
private final AccessTokenManager accessTokenManager;
|
||||
|
||||
@Override
|
||||
public Mono<ParsedToken> parseToken(ServerWebExchange exchange) {
|
||||
|
||||
String token = exchange.getRequest().getQueryParams().getFirst("access_token");
|
||||
if (StringUtils.isEmpty(token)) {
|
||||
token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||
if (StringUtils.hasText(token)) {
|
||||
String[] typeAndToken = token.split("[ ]");
|
||||
if (typeAndToken.length == 2 && typeAndToken[0].equalsIgnoreCase("bearer")) {
|
||||
token = typeAndToken[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(token)) {
|
||||
return Mono.just(ParsedToken.of("oauth2", token));
|
||||
}
|
||||
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> get(String userId) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> get() {
|
||||
return ContextUtils.reactiveContext()
|
||||
.flatMap(context ->
|
||||
context.get(ContextKey.of(ParsedToken.class))
|
||||
.filter(token -> "oauth2".equals(token.getType()))
|
||||
.map(t -> accessTokenManager
|
||||
.getAuthenticationByToken(t.getToken()))
|
||||
.orElse(Mono.empty()))
|
||||
.flatMap(auth -> ReactiveLogger.mdc("userId", auth.getUser().getId())
|
||||
.then(ReactiveLogger.mdc("username", auth.getUser().getName()))
|
||||
.thenReturn(auth));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.hswebframework.web.oauth2.server.code;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AuthorizationCodeCache implements Serializable {
|
||||
private static final long serialVersionUID = -6849794470754667710L;
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String code;
|
||||
|
||||
private Authentication authentication;
|
||||
|
||||
private String scope;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.hswebframework.web.oauth2.server.code;
|
||||
|
||||
import org.hswebframework.web.oauth2.server.AccessToken;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Granter;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 授权码模式认证
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 4.0.7
|
||||
*/
|
||||
public interface AuthorizationCodeGranter extends OAuth2Granter {
|
||||
|
||||
/**
|
||||
* 申请授权码
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 授权码信息
|
||||
*/
|
||||
Mono<AuthorizationCodeResponse> requestCode(AuthorizationCodeRequest request);
|
||||
|
||||
/**
|
||||
* 根据授权码获取token
|
||||
*
|
||||
* @param request 请求
|
||||
* @return token
|
||||
*/
|
||||
Mono<AccessToken> requestToken(AuthorizationCodeTokenRequest request);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.hswebframework.web.oauth2.server.code;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Client;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Request;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthorizationCodeRequest extends OAuth2Request {
|
||||
private OAuth2Client client;
|
||||
|
||||
private Authentication authentication;
|
||||
|
||||
|
||||
public AuthorizationCodeRequest(OAuth2Client client,
|
||||
Authentication authentication,
|
||||
Map<String, Object> parameters) {
|
||||
super(parameters);
|
||||
this.client = client;
|
||||
this.authentication = authentication;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.hswebframework.web.oauth2.server.code;
|
||||
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Client;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Request;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Response;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class AuthorizationCodeResponse extends OAuth2Response {
|
||||
private String code;
|
||||
|
||||
public AuthorizationCodeResponse(String code) {
|
||||
this.code = code;
|
||||
with("code", code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.hswebframework.web.oauth2.server.code;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Client;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Request;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class AuthorizationCodeTokenRequest extends OAuth2Request {
|
||||
|
||||
private OAuth2Client client;
|
||||
|
||||
public AuthorizationCodeTokenRequest(OAuth2Client client, Map<String, Object> parameters) {
|
||||
super(parameters);
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public Optional<String> code() {
|
||||
return getParameter("code").map(String::valueOf);
|
||||
}
|
||||
|
||||
public Optional<String> scope() {
|
||||
return getParameter("scope").map(String::valueOf);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.hswebframework.web.oauth2.server.code;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.id.IDGenerator;
|
||||
import org.hswebframework.web.oauth2.ErrorType;
|
||||
import org.hswebframework.web.oauth2.OAuth2Exception;
|
||||
import org.hswebframework.web.oauth2.server.AccessToken;
|
||||
import org.hswebframework.web.oauth2.server.AccessTokenManager;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Client;
|
||||
import org.hswebframework.web.oauth2.server.ScopePredicate;
|
||||
import org.hswebframework.web.oauth2.server.utils.OAuth2ScopeUtils;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class DefaultAuthorizationCodeGranter implements AuthorizationCodeGranter {
|
||||
|
||||
private final AccessTokenManager accessTokenManager;
|
||||
|
||||
private final ReactiveRedisOperations<String, AuthorizationCodeCache> redis;
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public DefaultAuthorizationCodeGranter(AccessTokenManager accessTokenManager, ReactiveRedisConnectionFactory connectionFactory) {
|
||||
this(accessTokenManager, new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext
|
||||
.newSerializationContext()
|
||||
.key((RedisSerializer) RedisSerializer.string())
|
||||
.value(RedisSerializer.java())
|
||||
.hashKey(RedisSerializer.string())
|
||||
.hashValue(RedisSerializer.java())
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<AuthorizationCodeResponse> requestCode(AuthorizationCodeRequest request) {
|
||||
OAuth2Client client = request.getClient();
|
||||
Authentication authentication = request.getAuthentication();
|
||||
AuthorizationCodeCache codeCache = new AuthorizationCodeCache();
|
||||
String code = IDGenerator.MD5.generate();
|
||||
request.getParameter("scope").map(String::valueOf).ifPresent(codeCache::setScope);
|
||||
codeCache.setCode(code);
|
||||
codeCache.setClientId(client.getClientId());
|
||||
ScopePredicate permissionPredicate = OAuth2ScopeUtils.createScopePredicate(codeCache.getScope());
|
||||
|
||||
codeCache.setAuthentication(authentication.copy((permission, action) -> permissionPredicate.test(permission.getId(), action), dimension -> true));
|
||||
|
||||
|
||||
return redis
|
||||
.opsForValue()
|
||||
.set(getRedisKey(code), codeCache, Duration.ofMinutes(5))
|
||||
.thenReturn(new AuthorizationCodeResponse(code));
|
||||
}
|
||||
|
||||
|
||||
private String getRedisKey(String code) {
|
||||
return "oauth2-code:" + code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<AccessToken> requestToken(AuthorizationCodeTokenRequest request) {
|
||||
|
||||
return Mono
|
||||
.justOrEmpty(request.code())
|
||||
.map(this::getRedisKey)
|
||||
.flatMap(redis.opsForValue()::get)
|
||||
.switchIfEmpty(Mono.error(() -> new OAuth2Exception(ErrorType.ILLEGAL_CODE)))
|
||||
.flatMap(cache -> redis
|
||||
.opsForValue()
|
||||
.delete(getRedisKey(cache.getCode()))
|
||||
.thenReturn(cache))
|
||||
.flatMap(cache -> {
|
||||
if (!request.getClient().getClientId().equals(cache.getClientId())) {
|
||||
return Mono.error(new OAuth2Exception(ErrorType.ILLEGAL_CLIENT_ID));
|
||||
}
|
||||
return accessTokenManager.createAccessToken(cache.getClientId(), cache.getAuthentication(), false);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.hswebframework.web.oauth2.server.impl;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.oauth2.server.ClientCredentialGranter;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2GrantService;
|
||||
import org.hswebframework.web.oauth2.server.code.AuthorizationCodeGranter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class CompositeOAuth2GrantService implements OAuth2GrantService {
|
||||
|
||||
private AuthorizationCodeGranter authorizationCodeGranter;
|
||||
|
||||
private ClientCredentialGranter clientCredentialGranter;
|
||||
|
||||
@Override
|
||||
public AuthorizationCodeGranter authorizationCode() {
|
||||
return authorizationCodeGranter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientCredentialGranter clientCredential() {
|
||||
return clientCredentialGranter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.hswebframework.web.oauth2.server.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.oauth2.server.AccessToken;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class RedisAccessToken implements Serializable {
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private String refreshToken;
|
||||
|
||||
private long createTime;
|
||||
|
||||
private Authentication authentication;
|
||||
|
||||
private boolean singleton;
|
||||
|
||||
public AccessToken toAccessToken(int expiresIn){
|
||||
AccessToken token=new AccessToken();
|
||||
token.setAccessToken(accessToken);
|
||||
token.setRefreshToken(refreshToken);
|
||||
token.setExpiresIn(expiresIn);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package org.hswebframework.web.oauth2.server.impl;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.oauth2.ErrorType;
|
||||
import org.hswebframework.web.oauth2.OAuth2Exception;
|
||||
import org.hswebframework.web.oauth2.server.AccessToken;
|
||||
import org.hswebframework.web.oauth2.server.AccessTokenManager;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
|
||||
public class RedisAccessTokenManager implements AccessTokenManager {
|
||||
|
||||
private final ReactiveRedisOperations<String, RedisAccessToken> tokenRedis;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private int tokenExpireIn = 7200;//2小时
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private int refreshExpireIn = 2592000; //30天
|
||||
|
||||
public RedisAccessTokenManager(ReactiveRedisOperations<String, RedisAccessToken> tokenRedis) {
|
||||
this.tokenRedis = tokenRedis;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public RedisAccessTokenManager(ReactiveRedisConnectionFactory connectionFactory) {
|
||||
this(new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext
|
||||
.newSerializationContext()
|
||||
.key((RedisSerializer) RedisSerializer.string())
|
||||
.value(RedisSerializer.java())
|
||||
.hashKey(RedisSerializer.string())
|
||||
.hashValue(RedisSerializer.java())
|
||||
.build()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Authentication> getAuthenticationByToken(String accessToken) {
|
||||
|
||||
return tokenRedis
|
||||
.opsForValue()
|
||||
.get(createTokenRedisKey(accessToken))
|
||||
.map(RedisAccessToken::getAuthentication);
|
||||
}
|
||||
|
||||
private String createTokenRedisKey(String token) {
|
||||
return "oauth2-token:" + token;
|
||||
}
|
||||
|
||||
private String createRefreshTokenRedisKey(String token) {
|
||||
return "oauth2-refresh-token:" + token;
|
||||
}
|
||||
|
||||
private String createSingletonTokenRedisKey(String clientId) {
|
||||
return "oauth2-" + clientId + "-token";
|
||||
}
|
||||
|
||||
private Mono<RedisAccessToken> doCreateAccessToken(String clientId, Authentication authentication, boolean singleton) {
|
||||
String token = DigestUtils.md5Hex(UUID.randomUUID().toString());
|
||||
String refresh = DigestUtils.md5Hex(UUID.randomUUID().toString());
|
||||
RedisAccessToken accessToken = new RedisAccessToken(clientId, token, refresh, System.currentTimeMillis(), authentication, singleton);
|
||||
|
||||
return storeToken(accessToken).thenReturn(accessToken);
|
||||
}
|
||||
|
||||
private Mono<Void> storeToken(RedisAccessToken token) {
|
||||
return Mono
|
||||
.zip(
|
||||
tokenRedis.opsForValue().set(createTokenRedisKey(token.getAccessToken()), token, Duration.ofSeconds(tokenExpireIn)),
|
||||
tokenRedis.opsForValue().set(createRefreshTokenRedisKey(token.getRefreshToken()), token, Duration.ofSeconds(refreshExpireIn))
|
||||
).then();
|
||||
}
|
||||
|
||||
private Mono<AccessToken> doCreateSingletonAccessToken(String clientId, Authentication authentication) {
|
||||
String redisKey = createSingletonTokenRedisKey(clientId);
|
||||
|
||||
return tokenRedis
|
||||
.opsForValue()
|
||||
.get(redisKey)
|
||||
.flatMap(token -> tokenRedis
|
||||
.getExpire(redisKey)
|
||||
.map(duration -> token.toAccessToken((int) (duration.toMillis() / 1000))))
|
||||
.switchIfEmpty(Mono.defer(() -> doCreateAccessToken(clientId, authentication, true)
|
||||
.flatMap(redisAccessToken -> tokenRedis
|
||||
.opsForValue()
|
||||
.set(redisKey, redisAccessToken, Duration.ofSeconds(tokenExpireIn))
|
||||
.thenReturn(redisAccessToken.toAccessToken(tokenExpireIn))))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<AccessToken> createAccessToken(String clientId,
|
||||
Authentication authentication,
|
||||
boolean singleton) {
|
||||
return singleton
|
||||
? doCreateSingletonAccessToken(clientId, authentication)
|
||||
: doCreateAccessToken(clientId, authentication, false).map(token -> token.toAccessToken(tokenExpireIn));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<AccessToken> refreshAccessToken(String clientId, String refreshToken) {
|
||||
String redisKey = createRefreshTokenRedisKey(refreshToken);
|
||||
|
||||
return tokenRedis
|
||||
.opsForValue()
|
||||
.get(redisKey)
|
||||
.switchIfEmpty(Mono.error(() -> new OAuth2Exception(ErrorType.EXPIRED_REFRESH_TOKEN)))
|
||||
.flatMap(token -> {
|
||||
if (!token.getClientId().equals(clientId)) {
|
||||
return Mono.error(new OAuth2Exception(ErrorType.ILLEGAL_CLIENT_ID));
|
||||
}
|
||||
//生成新token
|
||||
String accessToken = DigestUtils.md5Hex(UUID.randomUUID().toString());
|
||||
token.setAccessToken(accessToken);
|
||||
token.setCreateTime(System.currentTimeMillis());
|
||||
return storeToken(token)
|
||||
.as(result -> {
|
||||
// 单例token
|
||||
if (token.isSingleton()) {
|
||||
return tokenRedis
|
||||
.opsForValue()
|
||||
.set(createSingletonTokenRedisKey(clientId), token, Duration.ofSeconds(tokenExpireIn))
|
||||
.then(result);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.thenReturn(token.toAccessToken(tokenExpireIn));
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.hswebframework.web.oauth2.server.utils;
|
||||
|
||||
import org.hswebframework.web.oauth2.server.ScopePredicate;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author zhouhao
|
||||
* @since 4.0.8
|
||||
*/
|
||||
public class OAuth2ScopeUtils {
|
||||
|
||||
public static ScopePredicate createScopePredicate(String scopeStr) {
|
||||
if (StringUtils.isEmpty(scopeStr)) {
|
||||
return ((permission, action) -> false);
|
||||
}
|
||||
String[] scopes = scopeStr.split("[ ,\n]");
|
||||
Map<String, Set<String>> actions = new HashMap<>();
|
||||
for (String scope : scopes) {
|
||||
String[] permissions = scope.split("[:]");
|
||||
String per = permissions[0];
|
||||
Set<String> acts = actions.computeIfAbsent(per, k -> new HashSet<>());
|
||||
acts.addAll(Arrays.asList(permissions).subList(1, permissions.length));
|
||||
}
|
||||
|
||||
return ((permission, action) -> Optional
|
||||
.ofNullable(actions.get(permission))
|
||||
.map(acts -> action.length == 0 || acts.containsAll(Arrays.asList(action)))
|
||||
.orElse(false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.hswebframework.web.oauth2.server.web;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import org.hswebframework.web.authorization.Authentication;
|
||||
import org.hswebframework.web.authorization.annotation.Authorize;
|
||||
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
|
||||
import org.hswebframework.web.oauth2.ErrorType;
|
||||
import org.hswebframework.web.oauth2.OAuth2Exception;
|
||||
import org.hswebframework.web.oauth2.server.AccessToken;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Client;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2ClientManager;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2GrantService;
|
||||
import org.hswebframework.web.oauth2.server.code.AuthorizationCodeRequest;
|
||||
import org.hswebframework.web.oauth2.server.code.AuthorizationCodeTokenRequest;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/oauth2")
|
||||
@AllArgsConstructor
|
||||
@Tag(name = "OAuth2认证")
|
||||
public class OAuth2AuthorizeController {
|
||||
|
||||
private final OAuth2GrantService oAuth2GrantService;
|
||||
|
||||
private final OAuth2ClientManager clientManager;
|
||||
|
||||
|
||||
@GetMapping(value = "/authorize", params = "response_type=code")
|
||||
@Operation(summary = "申请授权码,并获取重定向地址", parameters = {
|
||||
@Parameter(name = "client_id", required = true),
|
||||
@Parameter(name = "redirect_uri", required = true),
|
||||
@Parameter(name = "state"),
|
||||
@Parameter(name = "response_type", description = "固定值为code")
|
||||
})
|
||||
public Mono<String> authorizeByCode(ServerWebExchange exchange) {
|
||||
Map<String, Object> param = new HashMap<>(exchange.getRequest().getQueryParams().toSingleValueMap());
|
||||
|
||||
return Authentication
|
||||
.currentReactive()
|
||||
.switchIfEmpty(Mono.error(UnAuthorizedException::new))
|
||||
.flatMap(auth -> this
|
||||
.getOAuth2Client((String) param.get("client_id"))
|
||||
.switchIfEmpty(Mono.error(() -> new OAuth2Exception(ErrorType.ILLEGAL_CLIENT_ID)))
|
||||
.flatMap(client -> {
|
||||
String redirectUri = (String) param.getOrDefault("redirect_uri", client.getRedirectUrl());
|
||||
client.validateRedirectUri(redirectUri);
|
||||
return oAuth2GrantService
|
||||
.authorizationCode()
|
||||
.requestCode(new AuthorizationCodeRequest(client, auth, param))
|
||||
.doOnNext(response -> {
|
||||
Optional.ofNullable(param.get("state")).ifPresent(state -> response.with("state", state));
|
||||
})
|
||||
.map(response -> buildRedirect(redirectUri, response.getParameters()));
|
||||
}));
|
||||
}
|
||||
|
||||
@GetMapping(value = "/token", params = "grant_type=authorization_code")
|
||||
@Operation(summary = "使用授权码申请token", parameters = {
|
||||
@Parameter(name = "client_id"),
|
||||
@Parameter(name = "client_secret"),
|
||||
@Parameter(name = "code"),
|
||||
@Parameter(name = "grant_type", description = "固定值为authorization_code")
|
||||
})
|
||||
@Authorize(ignore = true)
|
||||
public Mono<ResponseEntity<AccessToken>> requestTokenByCode(ServerWebExchange exchange) {
|
||||
Map<String, String> params = exchange.getRequest().getQueryParams().toSingleValueMap();
|
||||
|
||||
return doRequestCode(new HashMap<>(params))
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
private Mono<AccessToken> doRequestCode(Map<String, Object> param) {
|
||||
return this
|
||||
.getOAuth2Client((String) param.get("client_id"))
|
||||
.switchIfEmpty(Mono.error(() -> new OAuth2Exception(ErrorType.ILLEGAL_CLIENT_ID)))
|
||||
.flatMap(client -> oAuth2GrantService
|
||||
.authorizationCode()
|
||||
.requestToken(new AuthorizationCodeTokenRequest(client, param)));
|
||||
}
|
||||
|
||||
|
||||
@SneakyThrows
|
||||
public static String urlEncode(String url) {
|
||||
return URLEncoder.encode(url, "utf-8");
|
||||
}
|
||||
|
||||
static String buildRedirect(String redirectUri, Map<String, Object> params) {
|
||||
String paramsString = params.entrySet()
|
||||
.stream()
|
||||
.map(e -> e.getKey() + "=" + urlEncode(String.valueOf(e.getValue())))
|
||||
.collect(Collectors.joining("&"));
|
||||
if (redirectUri.contains("?")) {
|
||||
return redirectUri + "&" + paramsString;
|
||||
}
|
||||
return redirectUri + "?" + paramsString;
|
||||
}
|
||||
|
||||
|
||||
private Mono<OAuth2Client> getOAuth2Client(String id) {
|
||||
return clientManager.getClient(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.hswebframework.web.oauth2.server.OAuth2ServerAutoConfiguration
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class OAuth2ClientTest {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
OAuth2Client client=new OAuth2Client();
|
||||
|
||||
client.setRedirectUrl("http://hsweb.me/callback");
|
||||
|
||||
client.validateRedirectUri("http://hsweb.me/callback");
|
||||
|
||||
client.validateRedirectUri("http://hsweb.me/callback?a=1&n=1");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.hswebframework.web.oauth2.server;
|
||||
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
|
||||
public class RedisHelper {
|
||||
|
||||
public static LettuceConnectionFactory factory;
|
||||
|
||||
static {
|
||||
factory = new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1"));
|
||||
factory.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.hswebframework.web.oauth2.server.code;
|
||||
|
||||
import org.hswebframework.web.authorization.Permission;
|
||||
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
|
||||
import org.hswebframework.web.authorization.simple.SimplePermission;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Client;
|
||||
import org.hswebframework.web.oauth2.server.RedisHelper;
|
||||
import org.hswebframework.web.oauth2.server.impl.RedisAccessTokenManager;
|
||||
import org.junit.Test;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DefaultAuthorizationCodeGranterTest {
|
||||
|
||||
@Test
|
||||
public void testRequestToken() {
|
||||
|
||||
DefaultAuthorizationCodeGranter codeGranter = new DefaultAuthorizationCodeGranter(
|
||||
new RedisAccessTokenManager(RedisHelper.factory), RedisHelper.factory
|
||||
);
|
||||
|
||||
OAuth2Client client = new OAuth2Client();
|
||||
client.setClientId("test");
|
||||
client.setClientSecret("test");
|
||||
|
||||
codeGranter
|
||||
.requestCode(new AuthorizationCodeRequest(client, new SimpleAuthentication(), Collections.emptyMap()))
|
||||
.doOnNext(System.out::println)
|
||||
.flatMap(response -> codeGranter
|
||||
.requestToken(new AuthorizationCodeTokenRequest(client, Collections.singletonMap("code", response.getCode()))))
|
||||
.doOnNext(System.out::println)
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.hswebframework.web.oauth2.server.impl;
|
||||
|
||||
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
|
||||
import org.hswebframework.web.oauth2.server.RedisHelper;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class RedisAccessTokenManagerTest {
|
||||
|
||||
@Test
|
||||
public void testCreateAccessToken() {
|
||||
RedisAccessTokenManager tokenManager = new RedisAccessTokenManager(RedisHelper.factory);
|
||||
|
||||
SimpleAuthentication authentication = new SimpleAuthentication();
|
||||
|
||||
tokenManager.createAccessToken("test", authentication, false)
|
||||
.doOnNext(System.out::println)
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSingletonAccessToken() {
|
||||
RedisAccessTokenManager tokenManager = new RedisAccessTokenManager(RedisHelper.factory);
|
||||
|
||||
SimpleAuthentication authentication = new SimpleAuthentication();
|
||||
|
||||
Flux
|
||||
.concat(tokenManager
|
||||
.createAccessToken("test", authentication, true),
|
||||
tokenManager
|
||||
.createAccessToken("test", authentication, true))
|
||||
.doOnNext(System.out::println)
|
||||
.as(StepVerifier::create)
|
||||
.expectNextCount(2)
|
||||
.verifyComplete();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.hswebframework.web.oauth2.server.utils;
|
||||
|
||||
import org.hswebframework.web.oauth2.server.ScopePredicate;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class OAuth2ScopeUtilsTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testEmpty() {
|
||||
ScopePredicate predicate = OAuth2ScopeUtils.createScopePredicate(null);
|
||||
assertFalse(predicate.test("basic"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testScope() {
|
||||
ScopePredicate predicate = OAuth2ScopeUtils.createScopePredicate("basic user:info device:query");
|
||||
|
||||
assertTrue(predicate.test("basic"));
|
||||
{
|
||||
|
||||
assertTrue(predicate.test("user", "info"));
|
||||
assertFalse(predicate.test("user", "info2"));
|
||||
}
|
||||
|
||||
{
|
||||
assertTrue(predicate.test("device", "query"));
|
||||
assertFalse(predicate.test("device", "query2"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.hswebframework.web.oauth2.server.web;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class OAuth2AuthorizeControllerTest {
|
||||
|
||||
@Test
|
||||
public void testBuildRedirect() {
|
||||
String url = OAuth2AuthorizeController.buildRedirect("http://hsweb.me/callback", Collections.singletonMap("code", "1234"));
|
||||
|
||||
assertEquals(url,"http://hsweb.me/callback?code=1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildRedirectParam() {
|
||||
String url = OAuth2AuthorizeController.buildRedirect("http://hsweb.me/callback?a=b", Collections.singletonMap("code", "1234"));
|
||||
|
||||
assertEquals(url,"http://hsweb.me/callback?a=b&code=1234");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<logger name="io.netty" level="warn"/>
|
||||
|
||||
<logger name="io.lettuce" level="warn"/>
|
||||
<logger name="org.springframework" level="warn"/>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="debug">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<modules>
|
||||
<module>hsweb-authorization-api</module>
|
||||
<module>hsweb-authorization-basic</module>
|
||||
<module>hsweb-authorization-oauth2</module>
|
||||
</modules>
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-commons</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-commons</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.hswebframework.web.crud.service;
|
||||
|
||||
import org.hswebframework.ezorm.core.param.QueryParam;
|
||||
import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
|
||||
import org.hswebframework.utils.RandomUtil;
|
||||
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
|
||||
@@ -13,6 +12,8 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -30,13 +31,17 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
|
||||
default Mono<List<E>> queryResultToTree(QueryParamEntity paramEntity) {
|
||||
return query(paramEntity)
|
||||
.collectList()
|
||||
.map(list -> TreeSupportEntity.list2tree(list, this::setChildren, this::isRootNode));
|
||||
.map(list -> TreeSupportEntity.list2tree(list,
|
||||
this::setChildren,
|
||||
this::createRootNodePredicate));
|
||||
}
|
||||
|
||||
default Mono<List<E>> queryIncludeChildrenTree(QueryParamEntity paramEntity) {
|
||||
return queryIncludeChildren(paramEntity)
|
||||
.collectList()
|
||||
.map(list -> TreeSupportEntity.list2tree(list, this::setChildren, this::isRootNode));
|
||||
.map(list -> TreeSupportEntity.list2tree(list,
|
||||
this::setChildren,
|
||||
this::createRootNodePredicate));
|
||||
}
|
||||
|
||||
default Flux<E> queryIncludeChildren(Collection<K> idList) {
|
||||
@@ -115,6 +120,19 @@ public interface ReactiveTreeSortEntityService<E extends TreeSortSupportEntity<K
|
||||
return entity.getChildren();
|
||||
}
|
||||
|
||||
default Predicate<E> createRootNodePredicate(TreeSupportEntity.TreeHelper<E, K> helper) {
|
||||
return node -> {
|
||||
if (isRootNode(node)) {
|
||||
return true;
|
||||
}
|
||||
//有父节点,但是父节点不存在
|
||||
if (!StringUtils.isEmpty(node.getParentId())) {
|
||||
return helper.getNode(node.getParentId()) == null;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
default boolean isRootNode(E entity) {
|
||||
return StringUtils.isEmpty(entity.getParentId()) || "-1".equals(String.valueOf(entity.getParentId()));
|
||||
}
|
||||
|
||||
@@ -25,9 +25,5 @@ public class TestTreeSortEntityService extends GenericReactiveCrudService<TestTr
|
||||
return entity.getChildren();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRootNode(TestTreeSortEntity entity) {
|
||||
return entity.getParentId()==null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-concurrent</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -11,8 +11,7 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class ContextUtils {
|
||||
|
||||
private static ThreadLocal<Context> contextThreadLocal = ThreadLocal.withInitial(MapContext::new);
|
||||
|
||||
private static final ThreadLocal<Context> contextThreadLocal = ThreadLocal.withInitial(MapContext::new);
|
||||
|
||||
public static Context currentContext() {
|
||||
return contextThreadLocal.get();
|
||||
@@ -23,6 +22,8 @@ public class ContextUtils {
|
||||
.<Context>handle((context, sink) -> {
|
||||
if (context.hasKey(Context.class)) {
|
||||
sink.next(context.get(Context.class));
|
||||
}else {
|
||||
sink.complete();
|
||||
}
|
||||
})
|
||||
.subscriberContext(acceptContext(ctx -> {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-datasource</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-datasource</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
|
||||
</parent>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-datasource</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-logging</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-logging</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?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/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>hsweb-system-authorization</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>hsweb-system-authorization-oauth2</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-commons-crud</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-authorization-oauth2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-aspects</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-jdbc</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-authorization-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.hswebframework.web.oauth2.configuration;
|
||||
|
||||
import org.hswebframework.web.oauth2.server.OAuth2ClientManager;
|
||||
import org.hswebframework.web.oauth2.service.InDBOAuth2ClientManager;
|
||||
import org.hswebframework.web.oauth2.service.OAuth2ClientService;
|
||||
import org.hswebframework.web.oauth2.web.WebFluxOAuth2ClientController;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class OAuth2ClientManagerAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
|
||||
static class ReactiveOAuth2ClientManagerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public OAuth2ClientService oAuth2ClientService() {
|
||||
return new OAuth2ClientService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OAuth2ClientManager oAuth2ClientManager(OAuth2ClientService clientService) {
|
||||
return new InDBOAuth2ClientManager(clientService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebFluxOAuth2ClientController webFluxOAuth2ClientController(OAuth2ClientService clientService){
|
||||
return new WebFluxOAuth2ClientController(clientService);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.hswebframework.web.oauth2.entity;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
|
||||
import org.hswebframework.ezorm.rdb.mapping.annotation.DefaultValue;
|
||||
import org.hswebframework.ezorm.rdb.mapping.annotation.EnumCodec;
|
||||
import org.hswebframework.web.api.crud.entity.GenericEntity;
|
||||
import org.hswebframework.web.bean.ToString;
|
||||
import org.hswebframework.web.crud.generator.Generators;
|
||||
import org.hswebframework.web.oauth2.enums.OAuth2ClientState;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Client;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Table;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
@Table(name = "s_oauth2_client")
|
||||
@Getter
|
||||
@Setter
|
||||
public class OAuth2ClientEntity extends GenericEntity<String> {
|
||||
|
||||
@Column(length = 1024)
|
||||
@Schema(description = "Logo地址")
|
||||
private String logoUrl;
|
||||
|
||||
@Column(length = 64, nullable = false)
|
||||
@Schema(description = "客户端名称")
|
||||
@NotBlank
|
||||
private String name;
|
||||
|
||||
@Column(length = 128, nullable = false)
|
||||
@Schema(description = "密钥")
|
||||
@NotBlank
|
||||
@ToString.Ignore
|
||||
private String secret;
|
||||
|
||||
@Column(length = 64, nullable = false)
|
||||
@Schema(description = "绑定用户ID")
|
||||
@NotBlank
|
||||
private String userId;
|
||||
|
||||
@Column(length = 1024, nullable = false)
|
||||
@Schema(description = "回调地址")
|
||||
@NotBlank
|
||||
private String callbackUri;
|
||||
|
||||
@Column(length = 1024, nullable = false)
|
||||
@Schema(description = "首页地址")
|
||||
@NotBlank
|
||||
private String homeUri;
|
||||
|
||||
@Column
|
||||
@Schema(description = "说明")
|
||||
private String description;
|
||||
|
||||
@Column(length = 32)
|
||||
@EnumCodec
|
||||
@ColumnType(javaType = String.class)
|
||||
@DefaultValue("enabled")
|
||||
@Schema(description = "状态")
|
||||
private OAuth2ClientState state;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Schema(description = "创建时间")
|
||||
@DefaultValue(generator = Generators.CURRENT_TIME)
|
||||
private Long createTime;
|
||||
|
||||
public boolean enabled() {
|
||||
return state == OAuth2ClientState.enabled;
|
||||
}
|
||||
|
||||
public OAuth2Client toOAuth2Client() {
|
||||
OAuth2Client client = new OAuth2Client();
|
||||
client.setClientSecret(secret);
|
||||
client.setClientId(getId());
|
||||
client.setName(getName());
|
||||
client.setRedirectUrl(callbackUri);
|
||||
client.setDescription(description);
|
||||
client.setUserId(userId);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.hswebframework.web.oauth2.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.hswebframework.web.dict.EnumDict;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum OAuth2ClientState implements EnumDict<String> {
|
||||
|
||||
enabled("启用"),
|
||||
disabled("禁用");
|
||||
private final String text;
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return name();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.hswebframework.web.oauth2.service;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.hswebframework.web.oauth2.entity.OAuth2ClientEntity;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2Client;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2ClientManager;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class InDBOAuth2ClientManager implements OAuth2ClientManager {
|
||||
|
||||
private final OAuth2ClientService clientService;
|
||||
|
||||
@Override
|
||||
public Mono<OAuth2Client> getClient(String clientId) {
|
||||
return clientService
|
||||
.findById(clientId)
|
||||
.filter(OAuth2ClientEntity::enabled)
|
||||
.map(OAuth2ClientEntity::toOAuth2Client);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.hswebframework.web.oauth2.service;
|
||||
|
||||
import org.hswebframework.web.crud.service.GenericReactiveCacheSupportCrudService;
|
||||
import org.hswebframework.web.oauth2.entity.OAuth2ClientEntity;
|
||||
|
||||
public class OAuth2ClientService extends GenericReactiveCacheSupportCrudService<OAuth2ClientEntity, String> {
|
||||
|
||||
@Override
|
||||
public String getCacheName() {
|
||||
return "oauth2-client";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.hswebframework.web.oauth2.web;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.hswebframework.web.authorization.annotation.Resource;
|
||||
import org.hswebframework.web.crud.service.ReactiveCrudService;
|
||||
import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController;
|
||||
import org.hswebframework.web.oauth2.entity.OAuth2ClientEntity;
|
||||
import org.hswebframework.web.oauth2.service.OAuth2ClientService;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/oauth2/client")
|
||||
@AllArgsConstructor
|
||||
@Resource(id = "oauth2-client", name = "OAuth2客户端管理")
|
||||
@Tag(name = "OAuth2客户端管理")
|
||||
public class WebFluxOAuth2ClientController implements ReactiveServiceCrudController<OAuth2ClientEntity, String> {
|
||||
|
||||
private final OAuth2ClientService oAuth2ClientService;
|
||||
|
||||
@Override
|
||||
public ReactiveCrudService<OAuth2ClientEntity, String> getService() {
|
||||
return oAuth2ClientService;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.hswebframework.web.oauth2.configuration.OAuth2ClientManagerAutoConfiguration
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.hswebframework.web.oauth2;
|
||||
|
||||
import org.hswebframework.web.authorization.simple.DefaultAuthorizationAutoConfiguration;
|
||||
import org.hswebframework.web.crud.configuration.JdbcSqlExecutorConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(exclude = {
|
||||
//TransactionAutoConfiguration.class,
|
||||
JdbcSqlExecutorConfiguration.class,
|
||||
DataSourceAutoConfiguration.class
|
||||
})
|
||||
@ImportAutoConfiguration({
|
||||
R2dbcTransactionManagerAutoConfiguration.class,
|
||||
DefaultAuthorizationAutoConfiguration.class
|
||||
})
|
||||
public class ReactiveTestApplication {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.hswebframework.web.oauth2.configuration;
|
||||
|
||||
import org.hswebframework.web.oauth2.ReactiveTestApplication;
|
||||
import org.hswebframework.web.oauth2.server.OAuth2ClientManager;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = ReactiveTestApplication.class)
|
||||
public class OAuth2ClientManagerAutoConfigurationTest {
|
||||
|
||||
@Autowired
|
||||
OAuth2ClientManager clientManager;
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
assertNotNull(clientManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.hswebframework.web.oauth2.service;
|
||||
|
||||
import org.hswebframework.web.oauth2.ReactiveTestApplication;
|
||||
import org.hswebframework.web.oauth2.entity.OAuth2ClientEntity;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = ReactiveTestApplication.class)
|
||||
public class OAuth2ClientServiceTest {
|
||||
|
||||
@Autowired
|
||||
OAuth2ClientService clientService;
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
OAuth2ClientEntity clientEntity = new OAuth2ClientEntity();
|
||||
clientEntity.setId("test");
|
||||
clientEntity.setHomeUri("http://hsweb.me");
|
||||
clientEntity.setCallbackUri("http://hsweb.me/callback");
|
||||
clientEntity.setSecret("test");
|
||||
clientEntity.setName("test");
|
||||
clientEntity.setUserId("admin");
|
||||
clientService.insert(Mono.just(clientEntity))
|
||||
.as(StepVerifier::create)
|
||||
.expectNext(1)
|
||||
.verifyComplete();
|
||||
|
||||
clientService.findById("test")
|
||||
.doOnNext(System.out::println)
|
||||
.as(StepVerifier::create)
|
||||
.expectNextMatches(client -> {
|
||||
return client.getCreateTime() != null && client.getState() != null;
|
||||
}).verifyComplete();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
logging:
|
||||
level:
|
||||
org.hswebframework: debug
|
||||
org.springframework.transaction: debug
|
||||
org.springframework.data.r2dbc.connectionfactory: debug
|
||||
#spring:
|
||||
# r2dbc:
|
||||
spring:
|
||||
aop:
|
||||
proxy-target-class: true
|
||||
hsweb:
|
||||
authorize:
|
||||
auto-parse: true
|
||||
easyorm:
|
||||
default-schema: PUBLIC
|
||||
dialect: h2
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>pom</packaging>
|
||||
@@ -14,9 +14,9 @@
|
||||
<modules>
|
||||
<module>hsweb-system-authorization-api</module>
|
||||
<module>hsweb-system-authorization-default</module>
|
||||
<module>hsweb-system-authorization-oauth2</module>
|
||||
</modules>
|
||||
<artifactId>hsweb-system-authorization</artifactId>
|
||||
|
||||
|
||||
|
||||
</project>
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-system</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
11
pom.xml
11
pom.xml
@@ -24,7 +24,7 @@
|
||||
|
||||
<groupId>org.hswebframework.web</groupId>
|
||||
<artifactId>hsweb-framework</artifactId>
|
||||
<version>4.0.7</version>
|
||||
<version>4.0.8-SNAPSHOT</version>
|
||||
<modules>
|
||||
<module>hsweb-starter</module>
|
||||
<module>hsweb-core</module>
|
||||
@@ -84,13 +84,13 @@
|
||||
<javassist.version>3.20.0-GA</javassist.version>
|
||||
<activiti.version>5.19.0.2</activiti.version>
|
||||
|
||||
<fastjson.version>1.2.47</fastjson.version>
|
||||
<fastjson.version>1.2.74</fastjson.version>
|
||||
<h2.version>1.4.200</h2.version>
|
||||
<mysql.version>5.1.39</mysql.version>
|
||||
<cglib.version>3.2.2</cglib.version>
|
||||
<aspectj.version>1.6.12</aspectj.version>
|
||||
|
||||
<hsweb.ezorm.version>4.0.7</hsweb.ezorm.version>
|
||||
<hsweb.ezorm.version>4.0.6-SNAPSHOT</hsweb.ezorm.version>
|
||||
<hsweb.utils.version>3.0.2</hsweb.utils.version>
|
||||
<hsweb.expands.version>3.0.2</hsweb.expands.version>
|
||||
<swagger.version>2.7.0</swagger.version>
|
||||
@@ -301,7 +301,6 @@
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.1.7</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -360,7 +359,7 @@
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -391,7 +390,7 @@
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
<version>1.9.3</version>
|
||||
<version>1.9.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
Reference in New Issue
Block a user