Browse Source

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
zhou-hao 4 years ago
parent
commit
ffdcc2ce3d
90 changed files with 2155 additions and 127 deletions
  1. 2 2
      hsweb-authorization/hsweb-authorization-api/pom.xml
  2. 11 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Authentication.java
  3. 4 2
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Permission.java
  4. 1 4
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java
  5. 19 1
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java
  6. 11 3
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java
  7. 4 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ParsedToken.java
  8. 32 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ReactiveTokenAuthenticationSupplier.java
  9. 17 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/SimpleParsedToken.java
  10. 40 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/TokenAuthenticationManager.java
  11. 5 4
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenReactiveAuthenticationSupplier.java
  12. 1 1
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/event/UserTokenChangedEvent.java
  13. 61 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisTokenAuthenticationManager.java
  14. 119 66
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java
  15. 1 1
      hsweb-authorization/hsweb-authorization-basic/pom.xml
  16. 0 6
      hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizationController.java
  17. 52 0
      hsweb-authorization/hsweb-authorization-oauth2/pom.xml
  18. 108 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/ErrorType.java
  19. 32 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/GrantType.java
  20. 41 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Constants.java
  21. 19 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Exception.java
  22. 29 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/ResponseType.java
  23. 28 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessToken.java
  24. 16 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java
  25. 7 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/ClientCredentialGranter.java
  26. 38 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Client.java
  27. 9 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ClientManager.java
  28. 12 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2GrantService.java
  29. 7 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Granter.java
  30. 31 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Request.java
  31. 25 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Response.java
  32. 77 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java
  33. 10 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/ScopePredicate.java
  34. 61 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/auth/ReactiveOAuth2AccessTokenParser.java
  35. 26 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeCache.java
  36. 31 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeGranter.java
  37. 27 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeRequest.java
  38. 23 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeResponse.java
  39. 30 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeTokenRequest.java
  40. 86 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java
  41. 26 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/CompositeOAuth2GrantService.java
  42. 37 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessToken.java
  43. 143 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java
  44. 32 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/utils/OAuth2ScopeUtils.java
  45. 115 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java
  46. 3 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/resources/META-INF/spring.factories
  47. 20 0
      hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/OAuth2ClientTest.java
  48. 15 0
      hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/RedisHelper.java
  49. 42 0
      hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java
  50. 45 0
      hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManagerTest.java
  51. 35 0
      hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/utils/OAuth2ScopeUtilsTest.java
  52. 25 0
      hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeControllerTest.java
  53. 17 0
      hsweb-authorization/hsweb-authorization-oauth2/src/test/resources/logback.xml
  54. 2 1
      hsweb-authorization/pom.xml
  55. 1 1
      hsweb-commons/hsweb-commons-api/pom.xml
  56. 1 1
      hsweb-commons/hsweb-commons-crud/pom.xml
  57. 21 3
      hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/service/ReactiveTreeSortEntityService.java
  58. 0 4
      hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/service/TestTreeSortEntityService.java
  59. 1 1
      hsweb-commons/pom.xml
  60. 1 1
      hsweb-concurrent/hsweb-concurrent-cache/pom.xml
  61. 1 1
      hsweb-concurrent/pom.xml
  62. 1 1
      hsweb-core/pom.xml
  63. 3 2
      hsweb-core/src/main/java/org/hswebframework/web/context/ContextUtils.java
  64. 1 1
      hsweb-datasource/hsweb-datasource-api/pom.xml
  65. 1 1
      hsweb-datasource/hsweb-datasource-jta/pom.xml
  66. 1 1
      hsweb-datasource/hsweb-datasource-web/pom.xml
  67. 1 1
      hsweb-datasource/pom.xml
  68. 1 1
      hsweb-logging/hsweb-access-logging-aop/pom.xml
  69. 1 1
      hsweb-logging/hsweb-access-logging-api/pom.xml
  70. 1 1
      hsweb-logging/pom.xml
  71. 1 1
      hsweb-starter/pom.xml
  72. 1 1
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-api/pom.xml
  73. 1 1
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/pom.xml
  74. 84 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/pom.xml
  75. 37 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfiguration.java
  76. 84 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/entity/OAuth2ClientEntity.java
  77. 20 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/enums/OAuth2ClientState.java
  78. 21 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/InDBOAuth2ClientManager.java
  79. 12 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/OAuth2ClientService.java
  80. 26 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/web/WebFluxOAuth2ClientController.java
  81. 3 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/resources/META-INF/spring.factories
  82. 22 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/ReactiveTestApplication.java
  83. 24 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfigurationTest.java
  84. 46 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/service/OAuth2ClientServiceTest.java
  85. 16 0
      hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/resources/application.yml
  86. 2 2
      hsweb-system/hsweb-system-authorization/pom.xml
  87. 1 1
      hsweb-system/hsweb-system-dictionary/pom.xml
  88. 1 1
      hsweb-system/hsweb-system-file/pom.xml
  89. 1 1
      hsweb-system/pom.xml
  90. 5 6
      pom.xml

+ 2 - 2
hsweb-authorization/hsweb-authorization-api/pom.xml

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

+ 11 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Authentication.java

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

+ 4 - 2
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Permission.java

@@ -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);
+
     /**
      * 数据权限查找判断逻辑接口
      *

+ 1 - 4
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java

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

+ 19 - 1
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java

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

+ 11 - 3
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java

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

+ 4 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ParsedToken.java

@@ -15,4 +15,8 @@ public interface ParsedToken {
      * @return 令牌类型
      */
     String getType();
+
+    static ParsedToken of(String type, String token) {
+        return SimpleParsedToken.of(type, token);
+    }
 }

+ 32 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ReactiveTokenAuthenticationSupplier.java

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

+ 17 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/SimpleParsedToken.java

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

+ 40 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/TokenAuthenticationManager.java

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

+ 5 - 4
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenReactiveAuthenticationSupplier.java

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

+ 1 - 1
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/event/UserTokenChangedEvent.java

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

+ 61 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisTokenAuthenticationManager.java

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

+ 119 - 66
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisUserTokenManager.java

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

+ 1 - 1
hsweb-authorization/hsweb-authorization-basic/pom.xml

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

+ 0 - 6
hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizationController.java

@@ -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 - 0
hsweb-authorization/hsweb-authorization-oauth2/pom.xml

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

+ 108 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/ErrorType.java

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

+ 32 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/GrantType.java

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

+ 41 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Constants.java

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

+ 19 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Exception.java

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

+ 29 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/ResponseType.java

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

+ 28 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessToken.java

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

+ 16 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java

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

+ 7 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/ClientCredentialGranter.java

@@ -0,0 +1,7 @@
+package org.hswebframework.web.oauth2.server;
+
+public interface ClientCredentialGranter extends OAuth2Granter {
+
+
+
+}

+ 38 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Client.java

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

+ 9 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ClientManager.java

@@ -0,0 +1,9 @@
+package org.hswebframework.web.oauth2.server;
+
+import reactor.core.publisher.Mono;
+
+public interface OAuth2ClientManager {
+
+    Mono<OAuth2Client> getClient(String clientId);
+
+}

+ 12 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2GrantService.java

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

+ 7 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Granter.java

@@ -0,0 +1,7 @@
+package org.hswebframework.web.oauth2.server;
+
+public interface OAuth2Granter {
+
+
+
+}

+ 31 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Request.java

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

+ 25 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Response.java

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

+ 77 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java

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

+ 10 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/ScopePredicate.java

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

+ 61 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/auth/ReactiveOAuth2AccessTokenParser.java

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

+ 26 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeCache.java

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

+ 31 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeGranter.java

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

+ 27 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeRequest.java

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

+ 23 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeResponse.java

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

+ 30 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeTokenRequest.java

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

+ 86 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java

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

+ 26 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/CompositeOAuth2GrantService.java

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

+ 37 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessToken.java

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

+ 143 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java

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

+ 32 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/utils/OAuth2ScopeUtils.java

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

+ 115 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java

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

+ 3 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.hswebframework.web.oauth2.server.OAuth2ServerAutoConfiguration

+ 20 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/OAuth2ClientTest.java

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

+ 15 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/RedisHelper.java

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

+ 42 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java

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

+ 45 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManagerTest.java

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

+ 35 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/utils/OAuth2ScopeUtilsTest.java

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

+ 25 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeControllerTest.java

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

+ 17 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/test/resources/logback.xml

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

+ 2 - 1
hsweb-authorization/pom.xml

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

+ 1 - 1
hsweb-commons/hsweb-commons-api/pom.xml

@@ -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 - 1
hsweb-commons/hsweb-commons-crud/pom.xml

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

+ 21 - 3
hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/service/ReactiveTreeSortEntityService.java

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

+ 0 - 4
hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/service/TestTreeSortEntityService.java

@@ -25,9 +25,5 @@ public class TestTreeSortEntityService extends GenericReactiveCrudService<TestTr
         return entity.getChildren();
     }
 
-    @Override
-    public boolean isRootNode(TestTreeSortEntity entity) {
-        return entity.getParentId()==null;
-    }
 
 }

+ 1 - 1
hsweb-commons/pom.xml

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

+ 1 - 1
hsweb-concurrent/hsweb-concurrent-cache/pom.xml

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

+ 1 - 1
hsweb-concurrent/pom.xml

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

+ 1 - 1
hsweb-core/pom.xml

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

+ 3 - 2
hsweb-core/src/main/java/org/hswebframework/web/context/ContextUtils.java

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

+ 1 - 1
hsweb-datasource/hsweb-datasource-api/pom.xml

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

+ 1 - 1
hsweb-datasource/hsweb-datasource-jta/pom.xml

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

+ 1 - 1
hsweb-datasource/hsweb-datasource-web/pom.xml

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

+ 1 - 1
hsweb-datasource/pom.xml

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

+ 1 - 1
hsweb-logging/hsweb-access-logging-aop/pom.xml

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

+ 1 - 1
hsweb-logging/hsweb-access-logging-api/pom.xml

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

+ 1 - 1
hsweb-logging/pom.xml

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

+ 1 - 1
hsweb-starter/pom.xml

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

+ 1 - 1
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-api/pom.xml

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

+ 1 - 1
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-default/pom.xml

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

+ 84 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/pom.xml

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

+ 37 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfiguration.java

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

+ 84 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/entity/OAuth2ClientEntity.java

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

+ 20 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/enums/OAuth2ClientState.java

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

+ 21 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/InDBOAuth2ClientManager.java

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

+ 12 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/OAuth2ClientService.java

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

+ 26 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/web/WebFluxOAuth2ClientController.java

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

+ 3 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.hswebframework.web.oauth2.configuration.OAuth2ClientManagerAutoConfiguration

+ 22 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/ReactiveTestApplication.java

@@ -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 {
+
+
+}

+ 24 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfigurationTest.java

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

+ 46 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/service/OAuth2ClientServiceTest.java

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

+ 16 - 0
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/resources/application.yml

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

+ 2 - 2
hsweb-system/hsweb-system-authorization/pom.xml

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

+ 1 - 1
hsweb-system/hsweb-system-dictionary/pom.xml

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

+ 1 - 1
hsweb-system/hsweb-system-file/pom.xml

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

+ 1 - 1
hsweb-system/pom.xml

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

+ 5 - 6
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>