Repository: hs-web/hsweb-framework Branch: 5.0.x Commit: 47573d460bdf Files: 784 Total size: 1.8 MB Directory structure: gitextract_kk74s25r/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ ├── future.md │ │ └── qa.md │ └── workflows/ │ ├── maven-publish-4x.yml │ ├── maven-publish-5x.yml │ ├── pull_request.yml │ └── pull_request_5x.yml ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── build.sh ├── changes.sh ├── hsweb-authorization/ │ ├── README.md │ ├── hsweb-authorization-api/ │ │ ├── README.md │ │ ├── custom-data-access.md │ │ ├── define.md │ │ ├── pom.xml │ │ ├── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── org/ │ │ │ │ │ └── hswebframework/ │ │ │ │ │ └── web/ │ │ │ │ │ └── authorization/ │ │ │ │ │ ├── Authentication.java │ │ │ │ │ ├── AuthenticationHolder.java │ │ │ │ │ ├── AuthenticationManager.java │ │ │ │ │ ├── AuthenticationPredicate.java │ │ │ │ │ ├── AuthenticationRequest.java │ │ │ │ │ ├── AuthenticationSupplier.java │ │ │ │ │ ├── AuthenticationUtils.java │ │ │ │ │ ├── DefaultDimensionType.java │ │ │ │ │ ├── Dimension.java │ │ │ │ │ ├── DimensionProvider.java │ │ │ │ │ ├── DimensionType.java │ │ │ │ │ ├── Permission.java │ │ │ │ │ ├── ReactiveAuthenticationHolder.java │ │ │ │ │ ├── ReactiveAuthenticationInitializeService.java │ │ │ │ │ ├── ReactiveAuthenticationManager.java │ │ │ │ │ ├── ReactiveAuthenticationManagerProvider.java │ │ │ │ │ ├── ReactiveAuthenticationSupplier.java │ │ │ │ │ ├── Role.java │ │ │ │ │ ├── User.java │ │ │ │ │ ├── access/ │ │ │ │ │ │ ├── DataAccessConfig.java │ │ │ │ │ │ ├── DataAccessConfiguration.java │ │ │ │ │ │ ├── DataAccessController.java │ │ │ │ │ │ ├── DataAccessHandler.java │ │ │ │ │ │ ├── DataAccessType.java │ │ │ │ │ │ ├── DefaultDataAccessType.java │ │ │ │ │ │ ├── DimensionHelper.java │ │ │ │ │ │ ├── FieldFilterDataAccessConfig.java │ │ │ │ │ │ ├── OwnCreatedDataAccessConfig.java │ │ │ │ │ │ ├── ScopeDataAccessConfig.java │ │ │ │ │ │ └── UserAttachEntity.java │ │ │ │ │ ├── annotation/ │ │ │ │ │ │ ├── Authorize.java │ │ │ │ │ │ ├── CreateAction.java │ │ │ │ │ │ ├── DataAccess.java │ │ │ │ │ │ ├── DataAccessType.java │ │ │ │ │ │ ├── DeleteAction.java │ │ │ │ │ │ ├── Dimension.java │ │ │ │ │ │ ├── DimensionDataAccess.java │ │ │ │ │ │ ├── Dimensions.java │ │ │ │ │ │ ├── FieldDataAccess.java │ │ │ │ │ │ ├── Logical.java │ │ │ │ │ │ ├── QueryAction.java │ │ │ │ │ │ ├── RequiresRoles.java │ │ │ │ │ │ ├── Resource.java │ │ │ │ │ │ ├── ResourceAction.java │ │ │ │ │ │ ├── SaveAction.java │ │ │ │ │ │ ├── TwoFactor.java │ │ │ │ │ │ └── UserOwnData.java │ │ │ │ │ ├── builder/ │ │ │ │ │ │ ├── AuthenticationBuilder.java │ │ │ │ │ │ ├── AuthenticationBuilderFactory.java │ │ │ │ │ │ ├── DataAccessConfigBuilder.java │ │ │ │ │ │ └── DataAccessConfigBuilderFactory.java │ │ │ │ │ ├── context/ │ │ │ │ │ │ ├── AuthenticationThreadLocalAccessor.java │ │ │ │ │ │ └── ThreadLocalReactiveAuthenticationSupplier.java │ │ │ │ │ ├── define/ │ │ │ │ │ │ ├── AopAuthorizeDefinition.java │ │ │ │ │ │ ├── AuthorizeDefinition.java │ │ │ │ │ │ ├── AuthorizeDefinitionContext.java │ │ │ │ │ │ ├── AuthorizeDefinitionCustomizer.java │ │ │ │ │ │ ├── AuthorizeDefinitionInitializedEvent.java │ │ │ │ │ │ ├── AuthorizingContext.java │ │ │ │ │ │ ├── CompositeAuthorizeDefinitionCustomizer.java │ │ │ │ │ │ ├── DataAccessDefinition.java │ │ │ │ │ │ ├── DataAccessTypeDefinition.java │ │ │ │ │ │ ├── DimensionDefinition.java │ │ │ │ │ │ ├── DimensionsDefinition.java │ │ │ │ │ │ ├── HandleType.java │ │ │ │ │ │ ├── MergedAuthorizeDefinition.java │ │ │ │ │ │ ├── Phased.java │ │ │ │ │ │ ├── ResourceActionDefinition.java │ │ │ │ │ │ ├── ResourceDefinition.java │ │ │ │ │ │ └── ResourcesDefinition.java │ │ │ │ │ ├── dimension/ │ │ │ │ │ │ ├── DimensionManager.java │ │ │ │ │ │ ├── DimensionUserBind.java │ │ │ │ │ │ ├── DimensionUserBindProvider.java │ │ │ │ │ │ └── DimensionUserDetail.java │ │ │ │ │ ├── events/ │ │ │ │ │ │ ├── AbstractAuthorizationEvent.java │ │ │ │ │ │ ├── AuthorizationBeforeEvent.java │ │ │ │ │ │ ├── AuthorizationDecodeEvent.java │ │ │ │ │ │ ├── AuthorizationEvent.java │ │ │ │ │ │ ├── AuthorizationExitEvent.java │ │ │ │ │ │ ├── AuthorizationFailedEvent.java │ │ │ │ │ │ ├── AuthorizationInitializeEvent.java │ │ │ │ │ │ ├── AuthorizationSuccessEvent.java │ │ │ │ │ │ └── AuthorizingHandleBeforeEvent.java │ │ │ │ │ ├── exception/ │ │ │ │ │ │ ├── AccessDenyException.java │ │ │ │ │ │ ├── AuthenticationException.java │ │ │ │ │ │ ├── NeedTwoFactorException.java │ │ │ │ │ │ └── UnAuthorizedException.java │ │ │ │ │ ├── setting/ │ │ │ │ │ │ ├── SettingNullValueHolder.java │ │ │ │ │ │ ├── SettingValueHolder.java │ │ │ │ │ │ ├── StringSourceSettingHolder.java │ │ │ │ │ │ ├── UserSettingManager.java │ │ │ │ │ │ └── UserSettingPermission.java │ │ │ │ │ ├── simple/ │ │ │ │ │ │ ├── AbstractDataAccessConfig.java │ │ │ │ │ │ ├── CompositeReactiveAuthenticationManager.java │ │ │ │ │ │ ├── DefaultAuthorizationAutoConfiguration.java │ │ │ │ │ │ ├── DefaultDimensionManager.java │ │ │ │ │ │ ├── DimensionDataAccessConfig.java │ │ │ │ │ │ ├── PlainTextUsernamePasswordAuthenticationRequest.java │ │ │ │ │ │ ├── SimpleAuthentication.java │ │ │ │ │ │ ├── SimpleDimension.java │ │ │ │ │ │ ├── SimpleDimensionType.java │ │ │ │ │ │ ├── SimpleFieldFilterDataAccessConfig.java │ │ │ │ │ │ ├── SimpleOwnCreatedDataAccessConfig.java │ │ │ │ │ │ ├── SimplePermission.java │ │ │ │ │ │ ├── SimpleRole.java │ │ │ │ │ │ ├── SimpleUser.java │ │ │ │ │ │ └── builder/ │ │ │ │ │ │ ├── DataAccessConfigConverter.java │ │ │ │ │ │ ├── SimpleAuthenticationBuilder.java │ │ │ │ │ │ ├── SimpleAuthenticationBuilderFactory.java │ │ │ │ │ │ ├── SimpleDataAccessConfigBuilder.java │ │ │ │ │ │ └── SimpleDataAccessConfigBuilderFactory.java │ │ │ │ │ ├── token/ │ │ │ │ │ │ ├── AllopatricLoginMode.java │ │ │ │ │ │ ├── AuthenticationUserToken.java │ │ │ │ │ │ ├── DefaultUserTokenManager.java │ │ │ │ │ │ ├── LocalAuthenticationUserToken.java │ │ │ │ │ │ ├── LocalUserToken.java │ │ │ │ │ │ ├── ParsedToken.java │ │ │ │ │ │ ├── ReactiveTokenAuthenticationSupplier.java │ │ │ │ │ │ ├── SimpleParsedToken.java │ │ │ │ │ │ ├── ThirdPartAuthenticationManager.java │ │ │ │ │ │ ├── ThirdPartReactiveAuthenticationManager.java │ │ │ │ │ │ ├── TokenAuthenticationManager.java │ │ │ │ │ │ ├── TokenState.java │ │ │ │ │ │ ├── UserToken.java │ │ │ │ │ │ ├── UserTokenAuthenticationSupplier.java │ │ │ │ │ │ ├── UserTokenBeforeCreateEvent.java │ │ │ │ │ │ ├── UserTokenHolder.java │ │ │ │ │ │ ├── UserTokenManager.java │ │ │ │ │ │ ├── UserTokenReactiveAuthenticationSupplier.java │ │ │ │ │ │ ├── event/ │ │ │ │ │ │ │ ├── UserTokenChangedEvent.java │ │ │ │ │ │ │ ├── UserTokenCreatedEvent.java │ │ │ │ │ │ │ └── UserTokenRemovedEvent.java │ │ │ │ │ │ └── redis/ │ │ │ │ │ │ ├── RedisTokenAuthenticationManager.java │ │ │ │ │ │ ├── RedisUserTokenManager.java │ │ │ │ │ │ ├── SimpleAuthenticationUserToken.java │ │ │ │ │ │ └── SimpleUserToken.java │ │ │ │ │ └── twofactor/ │ │ │ │ │ ├── TwoFactorToken.java │ │ │ │ │ ├── TwoFactorTokenManager.java │ │ │ │ │ ├── TwoFactorValidator.java │ │ │ │ │ ├── TwoFactorValidatorManager.java │ │ │ │ │ ├── TwoFactorValidatorProvider.java │ │ │ │ │ └── defaults/ │ │ │ │ │ ├── DefaultTwoFactorValidator.java │ │ │ │ │ ├── DefaultTwoFactorValidatorManager.java │ │ │ │ │ ├── DefaultTwoFactorValidatorProvider.java │ │ │ │ │ ├── HashMapTwoFactorTokenManager.java │ │ │ │ │ └── UnsupportedTwoFactorValidator.java │ │ │ │ ├── java9/ │ │ │ │ │ └── module-info.java │ │ │ │ └── resources/ │ │ │ │ ├── META-INF/ │ │ │ │ │ ├── services/ │ │ │ │ │ │ └── io.micrometer.context.ThreadLocalAccessor │ │ │ │ │ └── spring/ │ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── i18n/ │ │ │ │ └── authentication/ │ │ │ │ ├── messages_en.properties │ │ │ │ └── messages_zh.properties │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── hswebframework/ │ │ │ └── web/ │ │ │ └── authorization/ │ │ │ ├── AuthenticationTests.java │ │ │ ├── UserTokenManagerTests.java │ │ │ ├── context/ │ │ │ │ └── AuthenticationThreadLocalAccessorTest.java │ │ │ ├── define/ │ │ │ │ └── MergedAuthorizeDefinitionTest.java │ │ │ ├── simple/ │ │ │ │ ├── DefaultDimensionManagerTest.java │ │ │ │ └── SimpleAuthenticationTest.java │ │ │ ├── token/ │ │ │ │ └── redis/ │ │ │ │ └── RedisUserTokenManagerTest.java │ │ │ └── twofactor/ │ │ │ └── defaults/ │ │ │ └── HashMapTwoFactorTokenManagerTest.java │ │ └── token.md │ ├── hsweb-authorization-basic/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── authorization/ │ │ │ │ └── basic/ │ │ │ │ ├── aop/ │ │ │ │ │ ├── AopAuthorizingController.java │ │ │ │ │ ├── AopMethodAuthorizeDefinitionCustomizerParser.java │ │ │ │ │ ├── AopMethodAuthorizeDefinitionParser.java │ │ │ │ │ └── DefaultAopMethodAuthorizeDefinitionParser.java │ │ │ │ ├── configuration/ │ │ │ │ │ ├── AopAuthorizeAutoConfiguration.java │ │ │ │ │ ├── AuthorizingHandlerAutoConfiguration.java │ │ │ │ │ ├── BasicAuthorizationTokenParser.java │ │ │ │ │ ├── EnableAopAuthorize.java │ │ │ │ │ └── WebMvcAuthorizingConfiguration.java │ │ │ │ ├── define/ │ │ │ │ │ ├── AopAuthorizeDefinitionParser.java │ │ │ │ │ ├── DefaultBasicAuthorizeDefinition.java │ │ │ │ │ ├── EmptyAuthorizeDefinition.java │ │ │ │ │ └── MergedAuthorizeDefinition.java │ │ │ │ ├── embed/ │ │ │ │ │ ├── EmbedAuthenticationInfo.java │ │ │ │ │ ├── EmbedAuthenticationManager.java │ │ │ │ │ ├── EmbedAuthenticationProperties.java │ │ │ │ │ └── EmbedReactiveAuthenticationManager.java │ │ │ │ ├── handler/ │ │ │ │ │ ├── AuthorizationLoginLoggerInfoHandler.java │ │ │ │ │ ├── AuthorizingHandler.java │ │ │ │ │ ├── DefaultAuthorizingHandler.java │ │ │ │ │ ├── UserAllowPermissionHandler.java │ │ │ │ │ └── access/ │ │ │ │ │ └── DimensionDataAccessHandler.java │ │ │ │ └── web/ │ │ │ │ ├── AuthorizationController.java │ │ │ │ ├── AuthorizedToken.java │ │ │ │ ├── BearerTokenParser.java │ │ │ │ ├── DefaultUserTokenGenPar.java │ │ │ │ ├── GeneratedToken.java │ │ │ │ ├── ReactiveUserTokenController.java │ │ │ │ ├── ReactiveUserTokenGenerator.java │ │ │ │ ├── ReactiveUserTokenParser.java │ │ │ │ ├── ServletUserTokenGenPar.java │ │ │ │ ├── SessionIdUserTokenGenerator.java │ │ │ │ ├── SessionIdUserTokenParser.java │ │ │ │ ├── UserOnSignIn.java │ │ │ │ ├── UserOnSignOut.java │ │ │ │ ├── UserTokenForTypeParser.java │ │ │ │ ├── UserTokenGenerator.java │ │ │ │ ├── UserTokenParser.java │ │ │ │ ├── UserTokenWebFilter.java │ │ │ │ └── WebUserTokenInterceptor.java │ │ │ ├── java9/ │ │ │ │ └── module-info.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ ├── additional-spring-configuration-metadata.json │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── hswebframework/ │ │ │ └── web/ │ │ │ └── authorization/ │ │ │ └── basic/ │ │ │ ├── aop/ │ │ │ │ ├── AopAuthorizingControllerTest.java │ │ │ │ ├── FluxTestController.java │ │ │ │ ├── TestApplication.java │ │ │ │ ├── TestController.java │ │ │ │ ├── TestDataAccess.java │ │ │ │ └── TestEntity.java │ │ │ ├── define/ │ │ │ │ └── DefaultBasicAuthorizeDefinitionTest.java │ │ │ └── web/ │ │ │ └── CompositeReactiveAuthenticationManagerTest.java │ │ └── resources/ │ │ └── application.yml │ ├── hsweb-authorization-oauth2/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── oauth2/ │ │ │ │ ├── ErrorType.java │ │ │ │ ├── GrantType.java │ │ │ │ ├── OAuth2Constants.java │ │ │ │ ├── OAuth2Exception.java │ │ │ │ ├── ResponseType.java │ │ │ │ └── server/ │ │ │ │ ├── AccessToken.java │ │ │ │ ├── AccessTokenManager.java │ │ │ │ ├── OAuth2Client.java │ │ │ │ ├── OAuth2ClientManager.java │ │ │ │ ├── OAuth2GrantService.java │ │ │ │ ├── OAuth2Granter.java │ │ │ │ ├── OAuth2Properties.java │ │ │ │ ├── OAuth2Request.java │ │ │ │ ├── OAuth2Response.java │ │ │ │ ├── OAuth2ServerAutoConfiguration.java │ │ │ │ ├── ScopePredicate.java │ │ │ │ ├── auth/ │ │ │ │ │ └── ReactiveOAuth2AccessTokenParser.java │ │ │ │ ├── code/ │ │ │ │ │ ├── AuthorizationCodeCache.java │ │ │ │ │ ├── AuthorizationCodeGranter.java │ │ │ │ │ ├── AuthorizationCodeRequest.java │ │ │ │ │ ├── AuthorizationCodeResponse.java │ │ │ │ │ ├── AuthorizationCodeTokenRequest.java │ │ │ │ │ └── DefaultAuthorizationCodeGranter.java │ │ │ │ ├── credential/ │ │ │ │ │ ├── ClientCredentialGranter.java │ │ │ │ │ ├── ClientCredentialRequest.java │ │ │ │ │ └── DefaultClientCredentialGranter.java │ │ │ │ ├── event/ │ │ │ │ │ └── OAuth2GrantedEvent.java │ │ │ │ ├── impl/ │ │ │ │ │ ├── CompositeOAuth2GrantService.java │ │ │ │ │ ├── RedisAccessToken.java │ │ │ │ │ └── RedisAccessTokenManager.java │ │ │ │ ├── refresh/ │ │ │ │ │ ├── DefaultRefreshTokenGranter.java │ │ │ │ │ ├── RefreshTokenGranter.java │ │ │ │ │ └── RefreshTokenRequest.java │ │ │ │ ├── utils/ │ │ │ │ │ └── OAuth2ScopeUtils.java │ │ │ │ └── web/ │ │ │ │ └── OAuth2AuthorizeController.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── hswebframework/ │ │ │ └── web/ │ │ │ └── oauth2/ │ │ │ └── server/ │ │ │ ├── OAuth2ClientTest.java │ │ │ ├── RedisHelper.java │ │ │ ├── code/ │ │ │ │ └── DefaultAuthorizationCodeGranterTest.java │ │ │ ├── impl/ │ │ │ │ └── RedisAccessTokenManagerTest.java │ │ │ ├── utils/ │ │ │ │ └── OAuth2ScopeUtilsTest.java │ │ │ └── web/ │ │ │ └── OAuth2AuthorizeControllerTest.java │ │ └── resources/ │ │ └── logback.xml │ └── pom.xml ├── hsweb-commons/ │ ├── hsweb-commons-api/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── api/ │ │ │ │ └── crud/ │ │ │ │ └── entity/ │ │ │ │ ├── Entity.java │ │ │ │ ├── EntityFactory.java │ │ │ │ ├── EntityFactoryHolder.java │ │ │ │ ├── EntityFactoryHolderConfiguration.java │ │ │ │ ├── ExtendableEntity.java │ │ │ │ ├── ExtendableTreeSortSupportEntity.java │ │ │ │ ├── GenericEntity.java │ │ │ │ ├── GenericI18nEntity.java │ │ │ │ ├── GenericTreeSortSupportEntity.java │ │ │ │ ├── ImplementFor.java │ │ │ │ ├── PagerResult.java │ │ │ │ ├── QueryNoPagingOperation.java │ │ │ │ ├── QueryOperation.java │ │ │ │ ├── QueryParamEntity.java │ │ │ │ ├── RecordCreationEntity.java │ │ │ │ ├── RecordModifierEntity.java │ │ │ │ ├── SortSupportEntity.java │ │ │ │ ├── TermExpressionParser.java │ │ │ │ ├── TransactionManagers.java │ │ │ │ ├── TreeSortSupportEntity.java │ │ │ │ ├── TreeSupportEntity.java │ │ │ │ └── TreeUtils.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── hswebframework/ │ │ └── web/ │ │ └── api/ │ │ └── crud/ │ │ └── entity/ │ │ ├── ExtendableEntityTest.java │ │ ├── TermExpressionParserTest.java │ │ └── TreeUtilsTest.java │ ├── hsweb-commons-crud/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── crud/ │ │ │ │ ├── annotation/ │ │ │ │ │ ├── DDL.java │ │ │ │ │ ├── EnableEasyormRepository.java │ │ │ │ │ ├── EnableEntityEvent.java │ │ │ │ │ └── Reactive.java │ │ │ │ ├── configuration/ │ │ │ │ │ ├── AutoDDLProcessor.java │ │ │ │ │ ├── CompositeEntityTableMetadataResolver.java │ │ │ │ │ ├── DefaultEntityResultWrapperFactory.java │ │ │ │ │ ├── DetectEntityColumnMapping.java │ │ │ │ │ ├── DialectProvider.java │ │ │ │ │ ├── DialectProviders.java │ │ │ │ │ ├── EasyormConfiguration.java │ │ │ │ │ ├── EasyormProperties.java │ │ │ │ │ ├── EasyormRepositoryRegistrar.java │ │ │ │ │ ├── EntityFactoryConfiguration.java │ │ │ │ │ ├── EntityInfo.java │ │ │ │ │ ├── EntityResultWrapperFactory.java │ │ │ │ │ ├── EntityTableMetadataResolver.java │ │ │ │ │ ├── JdbcSqlExecutorConfiguration.java │ │ │ │ │ ├── R2dbcSqlExecutorConfiguration.java │ │ │ │ │ ├── ReactiveRepositoryFactoryBean.java │ │ │ │ │ ├── SyncRepositoryFactoryBean.java │ │ │ │ │ └── TableMetadataCustomizer.java │ │ │ │ ├── entity/ │ │ │ │ │ └── factory/ │ │ │ │ │ ├── DefaultMapperFactory.java │ │ │ │ │ ├── DefaultPropertyCopier.java │ │ │ │ │ ├── EntityMappingCustomizer.java │ │ │ │ │ ├── MapperEntityFactory.java │ │ │ │ │ └── PropertyCopier.java │ │ │ │ ├── events/ │ │ │ │ │ ├── CompositeEventListener.java │ │ │ │ │ ├── CreatorEventListener.java │ │ │ │ │ ├── DefaultEntityEventListenerConfigure.java │ │ │ │ │ ├── EntityBeforeCreateEvent.java │ │ │ │ │ ├── EntityBeforeDeleteEvent.java │ │ │ │ │ ├── EntityBeforeModifyEvent.java │ │ │ │ │ ├── EntityBeforeQueryEvent.java │ │ │ │ │ ├── EntityBeforeSaveEvent.java │ │ │ │ │ ├── EntityCreatedEvent.java │ │ │ │ │ ├── EntityDDLEvent.java │ │ │ │ │ ├── EntityDeletedEvent.java │ │ │ │ │ ├── EntityEventHelper.java │ │ │ │ │ ├── EntityEventListener.java │ │ │ │ │ ├── EntityEventListenerConfigure.java │ │ │ │ │ ├── EntityEventListenerCustomizer.java │ │ │ │ │ ├── EntityEventPhase.java │ │ │ │ │ ├── EntityEventType.java │ │ │ │ │ ├── EntityModifyEvent.java │ │ │ │ │ ├── EntityPrepareCreateEvent.java │ │ │ │ │ ├── EntityPrepareModifyEvent.java │ │ │ │ │ ├── EntityPrepareSaveEvent.java │ │ │ │ │ ├── EntitySavedEvent.java │ │ │ │ │ ├── SqlExpressionInvoker.java │ │ │ │ │ ├── ValidateEventListener.java │ │ │ │ │ └── expr/ │ │ │ │ │ ├── AbstractSqlExpressionInvoker.java │ │ │ │ │ └── SpelSqlExpressionInvoker.java │ │ │ │ ├── exception/ │ │ │ │ │ └── DatabaseExceptionAnalyzerReporter.java │ │ │ │ ├── generator/ │ │ │ │ │ ├── CurrentTimeGenerator.java │ │ │ │ │ ├── DefaultIdGenerator.java │ │ │ │ │ ├── Generators.java │ │ │ │ │ ├── MD5Generator.java │ │ │ │ │ ├── RandomIdGenerator.java │ │ │ │ │ └── SnowFlakeStringIdGenerator.java │ │ │ │ ├── query/ │ │ │ │ │ ├── DefaultQueryHelper.java │ │ │ │ │ ├── JoinConditionalSpec.java │ │ │ │ │ ├── JoinNestConditionalSpec.java │ │ │ │ │ ├── JoinOnSpec.java │ │ │ │ │ ├── QueryAnalyzer.java │ │ │ │ │ ├── QueryAnalyzerImpl.java │ │ │ │ │ ├── QueryHelper.java │ │ │ │ │ ├── QueryHelperUtils.java │ │ │ │ │ └── ToHumpMap.java │ │ │ │ ├── service/ │ │ │ │ │ ├── CrudService.java │ │ │ │ │ ├── EnableCacheReactiveCrudService.java │ │ │ │ │ ├── GenericCrudService.java │ │ │ │ │ ├── GenericReactiveCacheSupportCrudService.java │ │ │ │ │ ├── GenericReactiveCrudService.java │ │ │ │ │ ├── GenericReactiveTreeSupportCrudService.java │ │ │ │ │ ├── GenericTreeSupportCrudService.java │ │ │ │ │ ├── ReactiveCrudService.java │ │ │ │ │ ├── ReactiveTreeSortEntityService.java │ │ │ │ │ ├── ReactiveTreeSortServiceHelper.java │ │ │ │ │ ├── SyncTreeSortServiceHelper.java │ │ │ │ │ ├── TreeSortEntityService.java │ │ │ │ │ └── TreeSortServiceHelper.java │ │ │ │ ├── sql/ │ │ │ │ │ ├── DefaultJdbcExecutor.java │ │ │ │ │ ├── DefaultJdbcReactiveExecutor.java │ │ │ │ │ ├── DefaultR2dbcExecutor.java │ │ │ │ │ └── terms/ │ │ │ │ │ └── TreeChildTermBuilder.java │ │ │ │ ├── utils/ │ │ │ │ │ └── TransactionUtils.java │ │ │ │ └── web/ │ │ │ │ ├── CommonErrorControllerAdvice.java │ │ │ │ ├── CommonWebFluxConfiguration.java │ │ │ │ ├── CommonWebMvcConfiguration.java │ │ │ │ ├── CommonWebMvcErrorControllerAdvice.java │ │ │ │ ├── CrudController.java │ │ │ │ ├── DeleteController.java │ │ │ │ ├── QueryController.java │ │ │ │ ├── R2dbcErrorControllerAdvice.java │ │ │ │ ├── ResponseMessage.java │ │ │ │ ├── ResponseMessageWrapper.java │ │ │ │ ├── ResponseMessageWrapperAdvice.java │ │ │ │ ├── SaveController.java │ │ │ │ ├── ServiceCrudController.java │ │ │ │ ├── ServiceDeleteController.java │ │ │ │ ├── ServiceQueryController.java │ │ │ │ ├── ServiceSaveController.java │ │ │ │ ├── TreeServiceQueryController.java │ │ │ │ └── reactive/ │ │ │ │ ├── ReactiveCrudController.java │ │ │ │ ├── ReactiveDeleteController.java │ │ │ │ ├── ReactiveQueryController.java │ │ │ │ ├── ReactiveSaveController.java │ │ │ │ ├── ReactiveServiceCrudController.java │ │ │ │ ├── ReactiveServiceDeleteController.java │ │ │ │ ├── ReactiveServiceQueryController.java │ │ │ │ ├── ReactiveServiceSaveController.java │ │ │ │ └── ReactiveTreeServiceQueryController.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ ├── services/ │ │ │ │ │ └── org.hswebframework.web.exception.analyzer.ExceptionAnalyzer │ │ │ │ └── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── i18n/ │ │ │ └── commons/ │ │ │ ├── messages_en.properties │ │ │ └── messages_zh.properties │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── hswebframework/ │ │ │ └── web/ │ │ │ └── crud/ │ │ │ ├── CrudTests.java │ │ │ ├── TestApplication.java │ │ │ ├── entity/ │ │ │ │ ├── CustomTestEntity.java │ │ │ │ ├── EventTestEntity.java │ │ │ │ ├── TestEntity.java │ │ │ │ └── TestTreeSortEntity.java │ │ │ ├── events/ │ │ │ │ ├── DefaultEntityEventListenerConfigureTest.java │ │ │ │ ├── EntityEventListenerTest.java │ │ │ │ ├── TestEntityListener.java │ │ │ │ └── expr/ │ │ │ │ └── SpelSqlExpressionInvokerTest.java │ │ │ ├── exception/ │ │ │ │ └── DatabaseExceptionAnalyzerReporterTest.java │ │ │ ├── query/ │ │ │ │ ├── DefaultQueryHelperTest.java │ │ │ │ ├── QueryAnalyzerImplTest.java │ │ │ │ └── QueryHelperUtilsTest.java │ │ │ └── service/ │ │ │ ├── CustomTestCustom.java │ │ │ ├── GenericReactiveCacheSupportCrudServiceTest.java │ │ │ ├── ReactiveTreeSortEntityServiceTest.java │ │ │ ├── TestCacheEntityService.java │ │ │ ├── TestEntityService.java │ │ │ ├── TestTreeChildTermBuilder.java │ │ │ └── TestTreeSortEntityService.java │ │ └── resources/ │ │ └── application.yml │ └── pom.xml ├── hsweb-concurrent/ │ ├── hsweb-concurrent-cache/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── cache/ │ │ │ │ ├── ReactiveCache.java │ │ │ │ ├── ReactiveCacheManager.java │ │ │ │ ├── ReactiveCacheResolver.java │ │ │ │ ├── configuration/ │ │ │ │ │ ├── ReactiveCacheManagerConfiguration.java │ │ │ │ │ └── ReactiveCacheProperties.java │ │ │ │ └── supports/ │ │ │ │ ├── AbstractReactiveCache.java │ │ │ │ ├── AbstractReactiveCacheManager.java │ │ │ │ ├── CaffeineReactiveCache.java │ │ │ │ ├── CaffeineReactiveCacheManager.java │ │ │ │ ├── GuavaReactiveCache.java │ │ │ │ ├── GuavaReactiveCacheManager.java │ │ │ │ ├── NullValue.java │ │ │ │ ├── RedisLocalReactiveCacheManager.java │ │ │ │ ├── RedisReactiveCache.java │ │ │ │ └── UnSupportedReactiveCache.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── hswebframework/ │ │ │ └── web/ │ │ │ └── cache/ │ │ │ ├── CaffeineReactiveCacheManagerTest.java │ │ │ ├── GuavaReactiveCacheManagerTest.java │ │ │ ├── RedisReactiveCacheManagerTest.java │ │ │ └── TestApplication.java │ │ └── resources/ │ │ └── application-redis.yml │ └── pom.xml ├── hsweb-core/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── hswebframework/ │ │ │ └── web/ │ │ │ ├── CodeConstants.java │ │ │ ├── aop/ │ │ │ │ ├── MethodInterceptorContext.java │ │ │ │ └── MethodInterceptorHolder.java │ │ │ ├── bean/ │ │ │ │ ├── BeanFactory.java │ │ │ │ ├── ClassDescription.java │ │ │ │ ├── ClassDescriptions.java │ │ │ │ ├── CompareUtils.java │ │ │ │ ├── Converter.java │ │ │ │ ├── Copier.java │ │ │ │ ├── DefaultToStringOperator.java │ │ │ │ ├── Diff.java │ │ │ │ ├── ExtendableToBeanCopier.java │ │ │ │ ├── ExtendableToMapCopier.java │ │ │ │ ├── ExtendableUtils.java │ │ │ │ ├── FastBeanCopier.java │ │ │ │ ├── MapToExtendableCopier.java │ │ │ │ ├── SingleValueMap.java │ │ │ │ ├── ToString.java │ │ │ │ └── ToStringOperator.java │ │ │ ├── context/ │ │ │ │ ├── Context.java │ │ │ │ ├── ContextHolder.java │ │ │ │ ├── ContextKey.java │ │ │ │ ├── ContextUtils.java │ │ │ │ ├── MapContext.java │ │ │ │ └── ThreadLocalContextHolderSupport.java │ │ │ ├── convert/ │ │ │ │ └── CustomMessageConverter.java │ │ │ ├── dict/ │ │ │ │ ├── ClassDictDefine.java │ │ │ │ ├── Dict.java │ │ │ │ ├── DictDefine.java │ │ │ │ ├── DictDefineRepository.java │ │ │ │ ├── EnumDict.java │ │ │ │ ├── I18nEnumDict.java │ │ │ │ ├── ItemDefine.java │ │ │ │ └── defaults/ │ │ │ │ ├── DefaultClassDictDefine.java │ │ │ │ ├── DefaultDictDefine.java │ │ │ │ ├── DefaultDictDefineRepository.java │ │ │ │ └── DefaultItemDefine.java │ │ │ ├── enums/ │ │ │ │ └── TrueOrFalse.java │ │ │ ├── event/ │ │ │ │ ├── AsyncEvent.java │ │ │ │ ├── AsyncEventHooks.java │ │ │ │ ├── DefaultAsyncEvent.java │ │ │ │ └── GenericsPayloadApplicationEvent.java │ │ │ ├── exception/ │ │ │ │ ├── BusinessException.java │ │ │ │ ├── I18nSupportException.java │ │ │ │ ├── NotFoundException.java │ │ │ │ ├── TraceSourceException.java │ │ │ │ ├── ValidationException.java │ │ │ │ └── analyzer/ │ │ │ │ ├── ExceptionAnalyzer.java │ │ │ │ ├── ExceptionAnalyzerReporter.java │ │ │ │ └── ExceptionAnalyzers.java │ │ │ ├── i18n/ │ │ │ │ ├── ContextLocaleResolver.java │ │ │ │ ├── I18nSupportEntity.java │ │ │ │ ├── I18nSupportUtils.java │ │ │ │ ├── LocaleThreadLocalAccessor.java │ │ │ │ ├── LocaleUtils.java │ │ │ │ ├── MessageSourceInitializer.java │ │ │ │ ├── MultipleI18nSupportEntity.java │ │ │ │ ├── SingleI18nSupportEntity.java │ │ │ │ ├── UnsupportedMessageSource.java │ │ │ │ └── WebFluxLocaleFilter.java │ │ │ ├── id/ │ │ │ │ ├── IDGenerator.java │ │ │ │ ├── RandomIdGenerator.java │ │ │ │ └── SnowflakeIdGenerator.java │ │ │ ├── logger/ │ │ │ │ └── ReactiveLogger.java │ │ │ ├── proxy/ │ │ │ │ └── Proxy.java │ │ │ ├── recycler/ │ │ │ │ ├── Recyclable.java │ │ │ │ ├── Recycler.java │ │ │ │ ├── RecyclerImpl.java │ │ │ │ └── Recyclers.java │ │ │ ├── utils/ │ │ │ │ ├── AnnotationUtils.java │ │ │ │ ├── CollectionUtils.java │ │ │ │ ├── DigestUtils.java │ │ │ │ ├── DynamicArrayList.java │ │ │ │ ├── ExpressionUtils.java │ │ │ │ ├── FluxCache.java │ │ │ │ ├── HttpParameterConverter.java │ │ │ │ ├── ModuleUtils.java │ │ │ │ ├── ReactiveWebUtils.java │ │ │ │ ├── TemplateParser.java │ │ │ │ └── WebUtils.java │ │ │ ├── validator/ │ │ │ │ ├── CreateGroup.java │ │ │ │ ├── UpdateGroup.java │ │ │ │ └── ValidatorUtils.java │ │ │ └── warn/ │ │ │ └── Warning.java │ │ ├── java9/ │ │ │ └── module-info.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── io.micrometer.context.ThreadLocalAccessor │ │ └── i18n/ │ │ └── core/ │ │ ├── messages_en.properties │ │ └── messages_zh.properties │ └── test/ │ └── java/ │ └── org/ │ └── hswebframework/ │ └── web/ │ ├── bean/ │ │ ├── Color.java │ │ ├── CompareUtilsTest.java │ │ ├── DiffTest.java │ │ ├── FastBeanCopierTest.java │ │ ├── NestObject.java │ │ ├── Source.java │ │ └── Target.java │ ├── dict/ │ │ ├── EnumDictTest.java │ │ ├── TestEnum.java │ │ └── TestEnumInteger.java │ ├── event/ │ │ └── EventTest.java │ ├── exception/ │ │ └── TraceSourceExceptionTest.java │ ├── i18n/ │ │ ├── I18nSupportUtilsTest.java │ │ ├── LocaleThreadLocalAccessorTest.java │ │ ├── LocaleUtilsTest.java │ │ └── MultipleI18nSupportEntityTest.java │ ├── id/ │ │ ├── IDGeneratorTests.java │ │ ├── RandomIdGeneratorTest.java │ │ └── SnowflakeIdGeneratorTest.java │ ├── logger/ │ │ └── ReactiveLoggerTest.java │ ├── recycler/ │ │ └── RecyclerImplTest.java │ ├── utils/ │ │ ├── CollectionUtilsTest.java │ │ ├── DigestUtilsTest.java │ │ └── TemplateParserTest.java │ └── validator/ │ └── ValidatorUtilsTest.java ├── hsweb-datasource/ │ ├── README.md │ ├── hsweb-datasource-api/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── datasource/ │ │ │ │ ├── AopDataSourceSwitcherAutoConfiguration.java │ │ │ │ ├── DataSourceHolder.java │ │ │ │ ├── DatabaseType.java │ │ │ │ ├── DynamicDataSource.java │ │ │ │ ├── DynamicDataSourceAutoConfiguration.java │ │ │ │ ├── DynamicDataSourceProxy.java │ │ │ │ ├── DynamicDataSourceService.java │ │ │ │ ├── HswebDataSourceProperties.java │ │ │ │ ├── JdbcDataSource.java │ │ │ │ ├── R2dbcDataSource.java │ │ │ │ ├── annotation/ │ │ │ │ │ ├── UseDataSource.java │ │ │ │ │ └── UseDefaultDataSource.java │ │ │ │ ├── config/ │ │ │ │ │ ├── DynamicDataSourceConfig.java │ │ │ │ │ ├── DynamicDataSourceConfigRepository.java │ │ │ │ │ └── InSpringDynamicDataSourceConfig.java │ │ │ │ ├── exception/ │ │ │ │ │ ├── DataSourceClosedException.java │ │ │ │ │ └── DataSourceNotFoundException.java │ │ │ │ ├── strategy/ │ │ │ │ │ ├── AnnotationDataSourceSwitchStrategyMatcher.java │ │ │ │ │ ├── CachedDataSourceSwitchStrategyMatcher.java │ │ │ │ │ ├── CachedTableSwitchStrategyMatcher.java │ │ │ │ │ ├── DataSourceSwitchStrategyMatcher.java │ │ │ │ │ ├── ExpressionDataSourceSwitchStrategyMatcher.java │ │ │ │ │ └── TableSwitchStrategyMatcher.java │ │ │ │ └── switcher/ │ │ │ │ ├── DataSourceSwitcher.java │ │ │ │ ├── DefaultJdbcSwitcher.java │ │ │ │ ├── DefaultR2dbcSwicher.java │ │ │ │ ├── DefaultReactiveSwitcher.java │ │ │ │ ├── DefaultSwitcher.java │ │ │ │ ├── JdbcSwitcher.java │ │ │ │ ├── R2dbcSwitcher.java │ │ │ │ ├── ReactiveSwitcher.java │ │ │ │ ├── SchemaSwitcher.java │ │ │ │ ├── Switcher.java │ │ │ │ └── TableSwitcher.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── hswebframework/ │ │ └── web/ │ │ └── datasource/ │ │ └── switcher/ │ │ ├── DefaultReactiveSwitcherTest.java │ │ └── DefaultSwitcherTest.java │ └── pom.xml ├── hsweb-logging/ │ ├── README.md │ ├── hsweb-access-logging-aop/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── hswebframework/ │ │ └── web/ │ │ └── logging/ │ │ └── aop/ │ │ ├── AccessLoggerParser.java │ │ ├── AopAccessLoggerSupport.java │ │ ├── AopAccessLoggerSupportAutoConfiguration.java │ │ ├── DefaultAccessLoggerParser.java │ │ ├── EnableAccessLogger.java │ │ ├── ReactiveAopAccessLoggerSupport.java │ │ ├── ResourceAccessLoggerParser.java │ │ ├── Swagger3AccessLoggerParser.java │ │ └── SwaggerAccessLoggerParser.java │ ├── hsweb-access-logging-api/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── org/ │ │ └── hswebframework/ │ │ └── web/ │ │ └── logging/ │ │ ├── AccessLogger.java │ │ ├── AccessLoggerHolder.java │ │ ├── AccessLoggerInfo.java │ │ ├── AccessLoggerListener.java │ │ ├── LoggerDefine.java │ │ ├── RequestInfo.java │ │ └── events/ │ │ ├── AccessLoggerAfterEvent.java │ │ └── AccessLoggerBeforeEvent.java │ └── pom.xml ├── hsweb-starter/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── hswebframework/ │ │ │ └── web/ │ │ │ └── starter/ │ │ │ ├── CorsAutoConfiguration.java │ │ │ ├── CorsProperties.java │ │ │ ├── i18n/ │ │ │ │ ├── CompositeMessageSource.java │ │ │ │ └── I18nConfiguration.java │ │ │ ├── jackson/ │ │ │ │ ├── CustomCodecsAutoConfiguration.java │ │ │ │ ├── CustomDeserializers.java │ │ │ │ ├── CustomJackson2JsonDecoder.java │ │ │ │ ├── CustomJackson2jsonEncoder.java │ │ │ │ ├── CustomMappingJackson2HttpMessageConverter.java │ │ │ │ ├── CustomTypeFactory.java │ │ │ │ └── Jackson2Tokenizer.java │ │ │ └── reporter/ │ │ │ └── GenericExceptionReport.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── spring.factories │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── hswebframework/ │ │ └── web/ │ │ └── starter/ │ │ ├── initialize/ │ │ │ ├── SystemInitializeTest.java │ │ │ └── TestApplication.java │ │ ├── jackson/ │ │ │ ├── CustomJackson2JsonDecoderTest.java │ │ │ ├── CustomJackson2jsonEncoderTest.java │ │ │ └── CustomTypeFactoryTest.java │ │ └── reporter/ │ │ └── GenericExceptionReportTest.java │ └── resources/ │ ├── hsweb-starter.js │ └── i18n/ │ ├── messages_en_US.properties │ └── messages_zh_CN.properties ├── hsweb-system/ │ ├── README.md │ ├── hsweb-system-authorization/ │ │ ├── README.md │ │ ├── hsweb-system-authorization-api/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ └── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── system/ │ │ │ │ └── authorization/ │ │ │ │ └── api/ │ │ │ │ ├── PasswordEncoder.java │ │ │ │ ├── PasswordValidator.java │ │ │ │ ├── UserDimensionProvider.java │ │ │ │ ├── UsernameValidator.java │ │ │ │ ├── entity/ │ │ │ │ │ ├── ActionEntity.java │ │ │ │ │ ├── AuthorizationSettingEntity.java │ │ │ │ │ ├── DataAccessEntity.java │ │ │ │ │ ├── DimensionEntity.java │ │ │ │ │ ├── DimensionTypeEntity.java │ │ │ │ │ ├── DimensionUserEntity.java │ │ │ │ │ ├── OptionalField.java │ │ │ │ │ ├── ParentPermission.java │ │ │ │ │ ├── PermissionEntity.java │ │ │ │ │ └── UserEntity.java │ │ │ │ ├── enums/ │ │ │ │ │ └── DimensionUserFeature.java │ │ │ │ ├── event/ │ │ │ │ │ ├── ClearUserAuthorizationCacheEvent.java │ │ │ │ │ ├── DimensionBindEvent.java │ │ │ │ │ ├── DimensionDeletedEvent.java │ │ │ │ │ ├── DimensionUnbindEvent.java │ │ │ │ │ ├── UserBeforeCreateEvent.java │ │ │ │ │ ├── UserCreatedEvent.java │ │ │ │ │ ├── UserDeletedEvent.java │ │ │ │ │ ├── UserModifiedEvent.java │ │ │ │ │ └── UserStateChangedEvent.java │ │ │ │ ├── request/ │ │ │ │ │ └── SaveUserRequest.java │ │ │ │ └── service/ │ │ │ │ ├── UserService.java │ │ │ │ └── reactive/ │ │ │ │ └── ReactiveUserService.java │ │ │ └── test/ │ │ │ └── java/ │ │ │ └── org/ │ │ │ └── hswebframework/ │ │ │ └── web/ │ │ │ └── system/ │ │ │ └── authorization/ │ │ │ └── api/ │ │ │ └── UsernameValidatorTest.java │ │ ├── hsweb-system-authorization-default/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── org/ │ │ │ │ │ └── hswebframework/ │ │ │ │ │ └── web/ │ │ │ │ │ └── system/ │ │ │ │ │ └── authorization/ │ │ │ │ │ └── defaults/ │ │ │ │ │ ├── configuration/ │ │ │ │ │ │ ├── AuthorizationServiceAutoConfiguration.java │ │ │ │ │ │ ├── AuthorizationWebAutoConfiguration.java │ │ │ │ │ │ └── PermissionProperties.java │ │ │ │ │ ├── service/ │ │ │ │ │ │ ├── AuthenticationInitializeCustomizer.java │ │ │ │ │ │ ├── AuthenticationInitializeProperties.java │ │ │ │ │ │ ├── DefaultAuthorizationSettingService.java │ │ │ │ │ │ ├── DefaultDimensionService.java │ │ │ │ │ │ ├── DefaultDimensionUserService.java │ │ │ │ │ │ ├── DefaultPermissionService.java │ │ │ │ │ │ ├── DefaultReactiveAuthenticationInitializeService.java │ │ │ │ │ │ ├── DefaultReactiveAuthenticationManager.java │ │ │ │ │ │ ├── DefaultReactiveUserService.java │ │ │ │ │ │ ├── DynamicDimension.java │ │ │ │ │ │ ├── PermissionSynchronization.java │ │ │ │ │ │ ├── RemoveUserTokenWhenUserDisabled.java │ │ │ │ │ │ └── terms/ │ │ │ │ │ │ ├── DimensionTerm.java │ │ │ │ │ │ └── UserDimensionTerm.java │ │ │ │ │ └── webflux/ │ │ │ │ │ ├── DimensionTypeResponse.java │ │ │ │ │ ├── WebFluxAuthorizationSettingController.java │ │ │ │ │ ├── WebFluxDimensionController.java │ │ │ │ │ ├── WebFluxDimensionTypeController.java │ │ │ │ │ ├── WebFluxDimensionUserController.java │ │ │ │ │ ├── WebFluxPermissionController.java │ │ │ │ │ └── WebFluxUserController.java │ │ │ │ └── resources/ │ │ │ │ ├── META-INF/ │ │ │ │ │ └── spring/ │ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ │ └── i18n/ │ │ │ │ └── authentication-default/ │ │ │ │ ├── messages_en.properties │ │ │ │ └── messages_zh.properties │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── system/ │ │ │ │ └── authorization/ │ │ │ │ └── defaults/ │ │ │ │ └── service/ │ │ │ │ ├── DefaultDimensionUserServiceTest.java │ │ │ │ └── reactive/ │ │ │ │ ├── DefaultReactiveAuthenticationManagerTest.java │ │ │ │ ├── DefaultReactiveUserServiceTest.java │ │ │ │ ├── ReactiveTestApplication.java │ │ │ │ └── WebFluxPermissionControllerTest.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ ├── hsweb-system-authorization-oauth2/ │ │ │ ├── pom.xml │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── java/ │ │ │ │ │ └── org/ │ │ │ │ │ └── hswebframework/ │ │ │ │ │ └── web/ │ │ │ │ │ └── oauth2/ │ │ │ │ │ ├── configuration/ │ │ │ │ │ │ └── OAuth2ClientManagerAutoConfiguration.java │ │ │ │ │ ├── entity/ │ │ │ │ │ │ └── OAuth2ClientEntity.java │ │ │ │ │ ├── enums/ │ │ │ │ │ │ └── OAuth2ClientState.java │ │ │ │ │ ├── service/ │ │ │ │ │ │ ├── InDBOAuth2ClientManager.java │ │ │ │ │ │ └── OAuth2ClientService.java │ │ │ │ │ └── web/ │ │ │ │ │ └── WebFluxOAuth2ClientController.java │ │ │ │ └── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── test/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── oauth2/ │ │ │ │ ├── ReactiveTestApplication.java │ │ │ │ ├── configuration/ │ │ │ │ │ └── OAuth2ClientManagerAutoConfigurationTest.java │ │ │ │ └── service/ │ │ │ │ └── OAuth2ClientServiceTest.java │ │ │ └── resources/ │ │ │ └── application.yml │ │ └── pom.xml │ ├── hsweb-system-dictionary/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── dictionary/ │ │ │ │ ├── configuration/ │ │ │ │ │ ├── DictionaryAutoConfiguration.java │ │ │ │ │ └── DictionaryProperties.java │ │ │ │ ├── entity/ │ │ │ │ │ ├── DictionaryEntity.java │ │ │ │ │ └── DictionaryItemEntity.java │ │ │ │ ├── event/ │ │ │ │ │ └── ClearDictionaryCacheEvent.java │ │ │ │ ├── service/ │ │ │ │ │ ├── CompositeDictDefineRepository.java │ │ │ │ │ ├── DefaultDictionaryItemService.java │ │ │ │ │ └── DefaultDictionaryService.java │ │ │ │ └── webflux/ │ │ │ │ ├── WebfluxDictionaryController.java │ │ │ │ └── WebfluxDictionaryItemController.java │ │ │ └── resources/ │ │ │ ├── META-INF/ │ │ │ │ └── spring/ │ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ │ └── i18n/ │ │ │ └── dictionary/ │ │ │ ├── messages_en.properties │ │ │ └── messages_zh.properties │ │ └── test/ │ │ └── java/ │ │ └── org/ │ │ └── hswebframework/ │ │ └── web/ │ │ └── dictionary/ │ │ ├── ReactiveTestApplication.java │ │ ├── configuration/ │ │ │ └── DictionaryAutoConfigurationTest.java │ │ └── service/ │ │ └── DefaultDictionaryItemServiceTest.java │ ├── hsweb-system-file/ │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── org/ │ │ │ │ └── hswebframework/ │ │ │ │ └── web/ │ │ │ │ └── file/ │ │ │ │ ├── FileServiceConfiguration.java │ │ │ │ ├── FileUploadProperties.java │ │ │ │ ├── service/ │ │ │ │ │ ├── FileStorageService.java │ │ │ │ │ └── LocalFileStorageService.java │ │ │ │ └── web/ │ │ │ │ └── ReactiveFileController.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── spring/ │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ └── test/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── hswebframework/ │ │ │ └── web/ │ │ │ └── file/ │ │ │ ├── FileUploadPropertiesTest.java │ │ │ ├── service/ │ │ │ │ └── LocalFileStorageServiceTest.java │ │ │ └── web/ │ │ │ ├── ReactiveFileControllerTest.java │ │ │ └── TestApplication.java │ │ └── resources/ │ │ └── test.json │ └── pom.xml ├── mvnw ├── mvnw.cmd └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: 提交Bug about: 提交bug,帮助我们更好完善项目. title: "[BUG]" labels: bug assignees: zhou-hao --- # BUG 说明 简要说明bug情况 # 运行环境 java: 1.8.0_131 maven: 3.3.9 hsweb: 3.0.5 # 复现步骤 # 期望结果 此功能期望的执行结果 # 截图说明 ================================================ FILE: .github/ISSUE_TEMPLATE/future.md ================================================ --- name: 需求 特性 about: 提出你想要的,帮助完善hsweb title: "[需求]" labels: 需求 assignees: zhou-hao --- # 场景 # 需求说明 ================================================ FILE: .github/ISSUE_TEMPLATE/qa.md ================================================ --- name: 疑问 帮助 about: 有任何疑问尽管提 title: "[疑问]" labels: 帮助 assignees: zhou-hao --- # 环境 java: 1.8.0_131 hsweb: 3.0.5 # 问题说明 ================================================ FILE: .github/workflows/maven-publish-4x.yml ================================================ name: Auto Deploy 4.x to the Maven Repository on: push: branches: ["master"] jobs: publish: runs-on: ubuntu-latest strategy: matrix: node_version: [ 18.x ] # os: [ubuntu-latest, windows-latest, macOS-latest] os: [ ubuntu-latest ] steps: - uses: actions/checkout@v4 with: fetch-depth: '2' - run: echo ${{github.ref}} - name: Set up Repository info uses: actions/setup-java@v4 with: java-version: '8' distribution: 'temurin' - name: Cache Maven Repository uses: actions/cache@v3 with: path: ~/.m2 key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} - name: Create Maven settings.xml #uses: actions/cache@v3 env: MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} run: | mkdir -p ~/.m2 echo " snapshots ${MAVEN_USERNAME} ${MAVEN_PASSWORD} " > ~/.m2/settings.xml # Step 4: 构建并发布到 Maven 私有仓库 - name: Build and Deploy to Maven run: mvn clean deploy -q -DskipTests -pl "$(./changes.sh)" ================================================ FILE: .github/workflows/maven-publish-5x.yml ================================================ name: Auto Deploy 5.x to the Maven Repository on: push: branches: ["5.0.x"] jobs: publish: runs-on: ubuntu-latest strategy: matrix: node_version: [ 18.x ] # os: [ubuntu-latest, windows-latest, macOS-latest] os: [ ubuntu-latest ] steps: - uses: actions/checkout@v4 with: fetch-depth: '2' - run: echo ${{github.ref}} - name: Set up Repository info uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Cache Maven Repository uses: actions/cache@v3 with: path: ~/.m2 key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} - name: Create Maven settings.xml #uses: actions/cache@v3 env: MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} run: | mkdir -p ~/.m2 echo " snapshots ${MAVEN_USERNAME} ${MAVEN_PASSWORD} " > ~/.m2/settings.xml # Step 4: 构建并发布到 Maven 私有仓库 - name: Build and Deploy to Maven run: mvn clean deploy -DskipTests -pl "$(./changes.sh)" ================================================ FILE: .github/workflows/pull_request.yml ================================================ name: Pull Request test master on: pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - name: Cache Maven Repository uses: actions/cache@v4.2.3 with: path: ~/.m2 key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} - name: Build with Maven run: ./mvnw test -q ================================================ FILE: .github/workflows/pull_request_5x.yml ================================================ name: Pull Request test 5.0.x on: pull_request: branches: [ 5.0.x ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up JDK 17 uses: actions/setup-java@v1 with: java-version: 17 - name: Cache Maven Repository uses: actions/cache@v4.2.3 with: path: ~/.m2 key: ${{ runner.os }}-${{ hashFiles('**/pom.xml') }} - name: Build with Maven run: ./mvnw test -q ================================================ FILE: .gitignore ================================================ **/pom.xml.versionsBackup **/target/ **/out/ **/log/ *.class # Mobile Tools for Java (J2ME) .mtj.tmp/ .idea/ /nbproject *.ipr *.iws *.iml # Package Files # *.jar *.war *.ear *.log # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* **/transaction-logs/ pom.xml.versionsBackup build/ !maven-wrapper.jar .java-version ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ distributionUrl=https://archive.apache.org/dist/maven/maven-3/3.9.3/binaries/apache-maven-3.9.3-bin.zip ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at admin@hsweb.me. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ # 贡献你的代码 1. fork 本仓库 2. 修改,增加代码 3. 执行`mvn test`通过 4. 提交`pull request` 5. 坐等审查 6. 合并 # BUG 如果知道导致bug的位置,你可以直接修改后`pull request`,也可以提交[issues](https://github.com/hs-web/hsweb-framework/issues/new).我们会尽快解决. # 需求&优化 你可以通过issues提交你希望`hsweb`增加的特性以及功能优化,并可以在 [projects](https://github.com/hs-web/hsweb-framework/projects)中查看`hsweb`的开发进展以及计划. # 社区&交流 你可以通过提交`issues`或者加入官方QQ群:[515649185](http://shang.qq.com/wpa/qunwpa?idkey=3d66b5dd14991d7645af694e7649b373f3a9ce1216131094c78cb2348d542c41) 以及发送邮件和我们取得联系. ================================================ FILE: ISSUE_TEMPLATE.md ================================================ 1. 问题描述: 2. 复现步骤: 3. 日志内容: ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 http://hsweb.me 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. ================================================ FILE: README.md ================================================ # hsweb4 基于spring-boot2,全响应式的后台管理框架 [](https://codecov.io/gh/hs-web/hsweb-framework/branch/master) [](https://travis-ci.com/hs-web/hsweb-framework) [](https://www.apache.org/licenses/LICENSE-2.0.html) # 功能,特性 - [x] 基于[r2dbc](https://github.com/r2dbc) ,[easy-orm](https://github.com/hs-web/hsweb-easy-orm/tree/4.0.x) 的通用响应式CRUD - [x] H2,Mysql,SqlServer,PostgreSQL - [x] 响应式r2dbc事务控制 - [x] 响应式权限控制,以及权限信息获取 - [x] RBAC权限控制 - [x] 数据权限控制 - [ ] 双因子验证 - [x] 多维度权限管理功能 - [x] 响应式缓存 - [ ] 非响应式支持(mvc,jdbc) - [ ] 内置业务功能 - [x] 权限管理 - [x] 用户管理 - [x] 权限设置 - [x] 权限分配 - [ ] 文件上传 - [x] 静态文件上传 - [ ] 文件秒传 - [x] 数据字典 # 示例 https://github.com/zhou-hao/hsweb4-examples ## 应用场景 1. 完全开源的后台管理系统. 2. 模块化的后台管理系统. 3. 功能可拓展的后台管理系统. 4. 集成各种常用功能的后台管理系统. 5. 前后分离的后台管理系统. 注意: 项目主要基于`spring-boot`,`spring-webflux`. 在使用`hsweb`之前,你应该对 [project-reactor](https://projectreactor.io/) , [spring-boot](https://github.com/spring-projects/spring-boot) 有一定的了解. 项目模块太多?不要被吓到.我们不推荐将本项目直接`clone`后修改,运行.而是使用maven依赖的方式使用`hsweb`. 选择自己需要的模块进行依赖,正式版发布后,所有模块都将发布到maven中央仓库. ## 文档 各个模块的使用方式查看对应模块下的 `README.md`,在使用之前, 你可以先粗略浏览一下各个模块,对每个模块的作用有大致的了解. ## 核心技术选型 1. Java 8 2. Maven3 3. Spring Boot 2.x 4. Project Reactor 响应式编程框架 5. hsweb easy orm 对r2dbc的orm封装 ## 模块简介 | 模块 | 说明 | | ------------- |:----------:| |[hsweb-authorization](hsweb-authorization)| 权限控制 | |[hsweb-commons](hsweb-commons) | 基础通用功能 | |[hsweb-concurrent](hsweb-concurrent)| 并发包,缓存,等 | |[hsweb-core](hsweb-core)| 框架核心,基础工具类 | |[hsweb-datasource](hsweb-datasource)| 数据源 | |[hsweb-logging](hsweb-logging)| 日志 | |[hsweb-starter](hsweb-starter)| 模块启动器 | |[hsweb-system](hsweb-system)| **系统常用功能** | ## 核心特性 1. 响应式,首个基于spring-webflux,r2dbc,从头到位的响应式. 2. DSL风格,可拓展的通用curd,支持前端直接传参数,无需担心任何sql注入. ```java //where name = #{name} createQuery() .where("name",name) .fetch(); //update s_user set name = #{user.name} where id = #{user.id} createUpdate() .set(user::getName) .where(user::getId) .execute(); ``` 3. 类JPA增删改 ```java @Table(name = "s_entity") public class MyEntity { @Id private String id; @Column private String name; @Column private Long createTime; } ``` 直接注入即可实现增删改查 ```java @Autowire private ReactiveRepository repository; ``` 2. 灵活的权限控制 ```java @PostMapping("/account") @SaveAction public Mono addAccount(@RequestBody Mono account){ return accountService.doSave(account); } ``` ## License [Apache 2.0](https://github.com/spring-projects/spring-boot/blob/main/LICENSE.txt) [](https://starchart.cc/hs-web/hsweb-framework) ================================================ FILE: build.sh ================================================ #!/usr/bin/env bash ./mvnw install -Dgit.commit.hash=$(git rev-parse HEAD) -DskipTests=true ================================================ FILE: changes.sh ================================================ #!/usr/bin/env bash # 收集变更模块 modules=$(git diff --name-only HEAD~1 HEAD | \ while read file; do dir=$(dirname "$file") while [ "$dir" != "." ] && [ "$dir" != "/" ]; do if [ -f "$dir/pom.xml" ]; then echo "$dir"; break; fi dir=$(dirname "$dir") done done | sort -u | tr '\n' ',' | sed 's/,$//') # 如果为空,则使用默认值 '.' if [ -z "$modules" ]; then echo "." else echo "$modules" fi ================================================ FILE: hsweb-authorization/README.md ================================================ # 授权认证模块 用于整个系统的授权认证管理 # 目录介绍 1. [hsweb-authorization-api](hsweb-authorization-api):权限控制API 3. [hsweb-authorization-basic](hsweb-authorization-basic):权限控制基础实现 ================================================ FILE: hsweb-authorization/hsweb-authorization-api/README.md ================================================ # 权限控制API 用于权限控制的API接口,支持RBAC权限控制,支持数据级(控制到行,列)权限控制. [用户令牌管理](token.md) [权限控制配置](define.md) # 介绍 以下讲到的类都是基于包:org.hswebframework.web.authorization ### 常用注解: _点击名称,查看源代码注释获得使用说明_ | 注解名称 | 说明 | | ------------- |:-------------:| | [`@Authorize`](src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java) | RBAC方式权限控制注解 | | [`@RequiresExpression`](src/main/java/org/hswebframework/web/authorization/annotation/RequiresExpression.java) | 表达式方式验证 | | [`@RequiresDataAccess`](src/main/java/org/hswebframework/web/authorization/annotation/RequiresDataAccess.java) | 数据权限控制 | [自定义数据权限控制](custom-data-access.md) ### 常用类 _点击名称,查看源代码注释获得使用说明_ | 类名 | 说明 | | ------------- |:-------------:| | [`Authentication`](src/main/java/org/hswebframework/web/authorization/Authentication.java) | 用户的认证信息 | | [`AuthenticationHolder`](src/main/java/org/hswebframework/web/authorization/AuthenticationHolder.java) | 用于获取当前登录用户的认证信息 | ### Listener api提供[AuthorizationListener](src/main/java/org/hswebframework/web/authorization/listener/AuthorizationListener.java) 来进行授权逻辑拓展,在授权前后执行可自定义的操作.如rsa解密帐号密码,验证码判断等。 默认事件列表(): | 类名 | 说明 | | ------------- |:-------------:| | [`AuthorizationDecodeEvent`](src/main/java/org/hswebframework/web/authorization/listener/event/AuthorizationDecodeEvent.java) | 接收到请求参数时 | | [`AuthorizationBeforeEvent`](src/main/java/org/hswebframework/web/authorization/listener/event/AuthorizationBeforeEvent.java) | 验证密码前触发 | | [`AuthorizationFailedEvent`](src/main/java/org/hswebframework/web/authorization/listener/event/AuthorizationFailedEvent.java) | 授权验证失败时触发 | | [`AuthorizationSuccessEvent`](src/main/java/org/hswebframework/web/authorization/listener/event/AuthorizationSuccessEvent.java) | 授权成功时触发 | | [`AuthorizationExitEvent`](src/main/java/org/hswebframework/web/authorization/listener/event/AuthorizationExitEvent.java) | 用户注销时触发 | 例子: ```java @Component public class CustomAuthorizationSuccessListener implements AuthorizationListener{ @Override public void on(AuthorizationSuccessEvent event) { Authentication authentication=event.getAuthentication(); //.... System.out.println(authentication.getUser().getName()+"登录啦"); } } ``` ================================================ FILE: hsweb-authorization/hsweb-authorization-api/custom-data-access.md ================================================ # 自定义拓展数据权限控制 1. 编写配置转换器,将在前端配置的内容转换为api需要的配置信息 实现 ``DataAccessConfigConvert``接口 ```java @org.springframework.stereotype.Component public class MyDataAccessConfigConvert implements DataAccessConfigConvert { @Override public boolean isSupport(String type, String action, String config) { return "custom_type".equals(type); } @Override public DataAccessConfig convert(String type, String action, String config) { MyDataAccessConfig accessConfig = JSON.parseObject(config, MyDataAccessConfig.class); accessConfig.setAction(action); accessConfig.setType(type); return accessConfig; } } ``` 2. 实现 ``DataAccessHandler``接口 ```java @org.springframework.stereotype.Component //提供给Spring才会生效 public class MyDataAccessHandler implements org.hswebframework.web.authorization.access.DataAccessHandler{ @Override public boolean isSupport(DataAccessConfig access) { //DataAccessConfig 在用户登录的时候,初始化 //DataAccessConfig 由 //支持的配置类型 return "custom_type".equals(access.getType()); } //处理请求,返回true表示授权通过 @Override public boolean handle(DataAccessConfig access, MethodInterceptorParamContext context) { //被拦截的方法参数 Map param= context.getNamedArguments(); // 判断逻辑 //... return true; } } ``` ================================================ FILE: hsweb-authorization/hsweb-authorization-api/define.md ================================================ # 权限配置定义 用于告诉权限框架哪些请求需要进行权限控制,怎么控制. ================================================ FILE: hsweb-authorization/hsweb-authorization-api/pom.xml ================================================ hsweb-authorization org.hswebframework.web 5.0.2-SNAPSHOT 4.0.0 ${project.artifactId} 授权,权限管理API hsweb-authorization-api org.hswebframework.web hsweb-core ${project.version} org.springframework.data spring-data-redis true io.lettuce lettuce-core test com.alibaba fastjson org.springframework.boot spring-boot-starter true io.swagger.core.v3 swagger-annotations jakarta.servlet jakarta.servlet-api true io.micrometer context-propagation true ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Authentication.java ================================================ /* * 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.authorization; import org.springframework.util.StringUtils; 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; /** * 用户授权信息,当前登录用户的权限信息,包括用户的基本信息,角色,权限集合等常用信息 * 获取方式: * * springmvc 入参方式: ResponseMessage myTest(Authorization auth){} * 静态方法方式:AuthorizationHolder.get(); * 响应式方式: return Authentication.currentReactive().map(auth->....) * * * @author zhouhao * @see ReactiveAuthenticationHolder * @see AuthenticationManager * @since 3.0 */ public interface Authentication extends Serializable { /** * 获取当前登录的用户权限信息 * * public Mono<User> getUser(){ * return Authentication.currentReactive() * .switchIfEmpty(Mono.error(new UnAuthorizedException())) * .flatMap(autz->findUserByUserId(autz.getUser().getId())); * } * * * @return 当前用户权限信息 * @see ReactiveAuthenticationHolder * @since 4.0 */ static Mono currentReactive() { return ReactiveAuthenticationHolder.get(); } /** * 非响应式环境适用 * * * Authentication auth= Authentication.current().get(); * //如果权限信息不存在将抛出{@link NoSuchElementException}建议使用下面的方式获取 * Authentication auth=Authentication.current().orElse(null); * //或者 * Authentication auth=Authentication.current().orElseThrow(UnAuthorizedException::new); * * * @return 当前用户权限信息 * @see Optional */ static Optional current() { return AuthenticationHolder.get(); } /** * @return 用户信息 */ User getUser(); /** * @return 用户所有维度 * @since 4.0 */ List getDimensions(); /** * @return 用户持有的权限集合 */ List getPermissions(); default boolean hasDimension(String type, String... id) { return hasAnyDimension(type, Arrays.asList(id)); } default boolean hasAllDimension(String type, Collection id) { if (id.isEmpty()) { return !getDimensions(type).isEmpty(); } return getDimensions(type) .stream() .allMatch(p -> id.contains(p.getId())); } default boolean hasAnyDimension(String type, Collection id) { if (id.isEmpty()) { return !getDimensions(type).isEmpty(); } return getDimensions(type) .stream() .anyMatch(p -> id.contains(p.getId())); } @Deprecated default boolean hasDimension(String type, Collection id) { if (id.isEmpty()) { return !getDimensions(type).isEmpty(); } return getDimensions(type) .stream() .anyMatch(p -> id.contains(p.getId())); } default boolean hasDimension(DimensionType type, String id) { return getDimension(type, id).isPresent(); } default Optional getDimension(String type, String id) { if (!StringUtils.hasText(type)) { return Optional.empty(); } return getDimensions() .stream() .filter(dimension -> dimension.getId().equals(id) && type.equalsIgnoreCase(dimension.getType().getId())) .findFirst(); } default Optional getDimension(DimensionType type, String id) { if (type == null) { return Optional.empty(); } return getDimensions() .stream() .filter(dimension -> dimension.getId().equals(id) && type.isSameType(dimension.getType())) .findFirst(); } default List getDimensions(String type) { if (!StringUtils.hasText(type)) { return Collections.emptyList(); } return getDimensions() .stream() .filter(dimension -> dimension.getType().isSameType(type)) .collect(Collectors.toList()); } default List getDimensions(DimensionType type) { if (type == null) { return Collections.emptyList(); } return getDimensions() .stream() .filter(dimension -> dimension.getType().isSameType(type)) .collect(Collectors.toList()); } /** * 根据权限id获取权限信息,权限不存在则返回null * * @param id 权限id * @return 权限信息 */ default Optional getPermission(String id) { if (null == id) { return Optional.empty(); } return getPermissions() .stream() .filter(permission -> permission.getId().equals(id)) .findAny(); } /** * 判断是否持有某权限以及对权限的可操作事件 * * @param permissionId 权限id {@link Permission#getId()} * @param actions 可操作动作 {@link Permission#getActions()} 如果为空,则不判断action,只判断permissionId * @return 是否持有权限 */ default boolean hasPermission(String permissionId, String... actions) { return hasPermission(permissionId, actions.length == 0 ? Collections.emptyList() : Arrays.asList(actions)); } default boolean hasPermission(String permissionId, Collection actions) { for (Permission permission : getPermissions()) { if (Objects.equals(permission.getId(), "*") || Objects.equals(permissionId, permission.getId())) { return actions.isEmpty() || permission.getActions().containsAll(actions) || permission.getActions().contains("*"); } } return false; } /** * 根据属性名获取属性值,返回一个{@link Optional}对象。 * 此方法可用于获取自定义的属性信息 * * @param name 属性名 * @param 属性值类型 * @return Optional属性值 */ Optional getAttribute(String name); /** * @return 全部属性集合 */ Map getAttributes(); /** * 设置属性,注意: 此属性可能并不会被持久化,仅用于临时传递信息. * * @param key key * @param value value */ default void setAttribute(String key, Serializable value) { getAttributes().put(key, value); } /** * 合并权限 * * @param source 源权限信息 * @return 合并后的信息 */ Authentication merge(Authentication source); /** * copy为新的权限信息 * * @param permissionFilter 权限过滤 * @param dimension 维度过滤 * @return 新的权限信息 */ Authentication copy(BiPredicate permissionFilter, Predicate dimension); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/AuthenticationHolder.java ================================================ /* * 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.authorization; import io.netty.util.concurrent.FastThreadLocal; import lombok.SneakyThrows; import org.hswebframework.web.authorization.simple.SimpleAuthentication; import reactor.core.Disposable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; import java.util.stream.Collectors; /** * 权限获取器,用于静态方式获取当前登录用户的权限信息. * 例如: * * @RequestMapping("/example") * public ResponseMessage example(){ * Authorization auth = AuthorizationHolder.get(); * return ResponseMessage.ok(); * } * * * @author zhouhao * @see AuthenticationSupplier * @since 3.0 */ public final class AuthenticationHolder { private static final List suppliers = new ArrayList<>(); private static final ReadWriteLock lock = new ReentrantReadWriteLock(); private static final FastThreadLocal CURRENT = new FastThreadLocal<>(); private static Optional get(Function> function) { int size = suppliers.size(); if (size == 0) { return Optional.empty(); } if (size == 1) { return function.apply(suppliers.get(0)); } AuthenticationUtils.AuthenticationMerging merging = new AuthenticationUtils.AuthenticationMerging(); for (AuthenticationSupplier supplier : suppliers) { function.apply(supplier).ifPresent(merging::merge); } return Optional.ofNullable(merging.get()); } /** * @return 当前登录的用户权限信息 */ public static Optional get() { Authentication current = CURRENT.getIfExists(); if (current != null) { return Optional.of(current); } return get(AuthenticationSupplier::get); } /** * 获取指定用户的权限信息 * * @param userId 用户ID * @return 权限信息 */ public static Optional get(String userId) { return get(supplier -> supplier.get(userId)); } /** * 初始化 {@link AuthenticationSupplier} * * @param supplier 认证信息提供者 */ public static void addSupplier(AuthenticationSupplier supplier) { lock.writeLock().lock(); try { suppliers.add(supplier); } finally { lock.writeLock().unlock(); } } public static void resetCurrent() { CURRENT.remove(); } public static void makeCurrent(Authentication authentication) { if (authentication == null) { resetCurrent(); } else { CURRENT.set(authentication); } } /** * 指定用户权限,执行一个任务。任务执行过程中可通过 {@link Authentication#current()}获取到当前权限. * * @param current 当前用户权限信息 * @param callable 任务执行器 * @param 任务执行结果类型 * @return 任务执行结果 */ @SneakyThrows public static T executeWith(Authentication current, Callable callable) { Authentication previous = CURRENT.getIfExists(); try { CURRENT.set(current); return callable.call(); } finally { CURRENT.set(previous); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/AuthenticationManager.java ================================================ /* * 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.authorization; import java.util.Optional; /** * 授权信息管理器,用于获取用户授权和同步授权信息 * * @author zhouhao * @see 3.0 */ public interface AuthenticationManager { /** * 进行授权操作 * * @param request 授权请求 * @return 授权成功则返回用户权限信息 */ Authentication authenticate(AuthenticationRequest request); /** * 根据用户ID获取权限信息 * * @param userId 用户ID * @return 权限信息 */ Optional getByUserId(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/AuthenticationPredicate.java ================================================ package org.hswebframework.web.authorization; import org.hswebframework.web.authorization.exception.AccessDenyException; import java.util.Arrays; import java.util.Objects; import java.util.function.Predicate; /** * @author zhouhao * @since 3.0 */ @FunctionalInterface public interface AuthenticationPredicate extends Predicate { static AuthenticationPredicate has(String permissionString) { return AuthenticationUtils.createPredicate(permissionString); } static AuthenticationPredicate dimension(String dimension, String... id) { return autz -> autz.hasAnyDimension(dimension, Arrays.asList(id)); } static AuthenticationPredicate permission(String permissionId, String... actions) { return autz -> autz.hasPermission(permissionId, actions); } default AuthenticationPredicate and(String permissionString) { return and(has(permissionString)); } default AuthenticationPredicate or(String permissionString) { return or(has(permissionString)); } @Override default AuthenticationPredicate and(Predicate super Authentication> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } @Override default AuthenticationPredicate or(Predicate super Authentication> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } default void assertHas(Authentication authentication) { if (!test(authentication)) { throw new AccessDenyException(); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/AuthenticationRequest.java ================================================ package org.hswebframework.web.authorization; import java.io.Serializable; /** * @author zhouhao * @since 3.0.0-RC */ public interface AuthenticationRequest extends Serializable { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/AuthenticationSupplier.java ================================================ /* * 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.authorization; import java.util.Optional; import java.util.function.Supplier; /** * @author zhouhao * @see Supplier * @see Authentication * @see ReactiveAuthenticationHolder */ public interface AuthenticationSupplier extends Supplier> { Optional get(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/AuthenticationUtils.java ================================================ package org.hswebframework.web.authorization; import org.hswebframework.web.authorization.simple.SimpleAuthentication; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * @author zhouhao * @since 3.0 */ public class AuthenticationUtils { public static Mono merge(Flux authenticationFlux){ return authenticationFlux .collect(AuthenticationMerging::new, AuthenticationMerging::merge) .mapNotNull(AuthenticationMerging::get); } static class AuthenticationMerging { private Authentication auth; private int count; public synchronized void merge(Authentication auth) { if (this.auth == null || this.auth == auth) { this.auth = auth; } else { if (count++ == 0) { SimpleAuthentication newAuth = new SimpleAuthentication(); newAuth.merge(this.auth); this.auth = newAuth; } this.auth.merge(auth); } } Authentication get() { return auth; } } public static AuthenticationPredicate createPredicate(String expression) { if (ObjectUtils.isEmpty(expression)) { return (authentication -> false); } AuthenticationPredicate main = null; // resource:user:add or update AuthenticationPredicate temp = null; boolean lastAnd = true; for (String conf : expression.split("[ ]")) { if (conf.startsWith("resource:")||conf.startsWith("permission:")) { String[] permissionAndActions = conf.split("[:]", 2); if (permissionAndActions.length < 2) { temp = authentication -> !authentication.getPermissions().isEmpty(); } else { String[] real = permissionAndActions[1].split("[:]"); temp = real.length > 1 ? AuthenticationPredicate.permission(real[0], real[1].split("[,]")) : AuthenticationPredicate.permission(real[0]); } } else if (main != null && conf.equalsIgnoreCase("and")) { lastAnd = true; main = main.and(temp); } else if (main != null && conf.equalsIgnoreCase("or")) { main = main.or(temp); lastAnd = false; } else { String[] real = conf.split("[:]", 2); if (real.length < 2) { temp = AuthenticationPredicate.dimension(real[0]); } else { temp = AuthenticationPredicate.dimension(real[0], real[1].split(",")); } } if (main == null) { main = temp; } } return main == null ? a -> false : (lastAnd ? main.and(temp) : main.or(temp)); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/DefaultDimensionType.java ================================================ package org.hswebframework.web.authorization; import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public enum DefaultDimensionType implements DimensionType { user("用户"), role("角色"); private String name; @Override public String getId() { return name(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Dimension.java ================================================ package org.hswebframework.web.authorization; import org.hswebframework.web.authorization.simple.SimpleDimension; import java.io.Serializable; import java.util.Map; import java.util.Optional; public interface Dimension extends Serializable { String getId(); String getName(); DimensionType getType(); Map getOptions(); default Optional getOption(String key) { return Optional.ofNullable(getOptions()) .map(ops -> ops.get(key)) .map(o -> (T) o); } default boolean typeIs(DimensionType type) { return this.getType() == type || this.getType().getId().equals(type.getId()); } default boolean typeIs(String type) { return this.getType().getId().equals(type); } static Dimension of(String id, String name, DimensionType type) { return of(id, name, type, null); } static Dimension of(String id, String name, DimensionType type, Map options) { return SimpleDimension.of(id, name, type, options); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/DimensionProvider.java ================================================ package org.hswebframework.web.authorization; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Collection; /** * 维度提供商,用户管理维度信息 * * @author zhouhao * @since 4.0 */ public interface DimensionProvider { /** * 获取全部支持的维度 * * @return 全部支持的维度 */ Flux extends DimensionType> getAllType(); /** * 获取用户获取维度信息 * * @param userId 用户ID * @return 维度列表 */ Flux extends Dimension> getDimensionByUserId(String userId); /** * 根据维度类型和ID获取维度信息 * * @param type 类型 * @param id ID * @return 维度信息 */ Mono extends Dimension> getDimensionById(DimensionType type, String id); /** * 根据维度类型和Id获取多个维度 * @param type 类型 * @param idList ID * @return 维度信息 */ default Flux extends Dimension> getDimensionsById(DimensionType type, Collection idList){ return Flux .fromIterable(idList) .flatMap(id->this.getDimensionById(type,id)); } /** * 根据维度ID获取用户ID * * @param dimensionId 维度ID * @return 用户ID */ Flux getUserIdByDimensionId(String dimensionId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/DimensionType.java ================================================ package org.hswebframework.web.authorization; public interface DimensionType { String getId(); String getName(); default boolean isSameType(DimensionType another) { return this == another || isSameType(another.getId()); } default boolean isSameType(String anotherId) { return this.getId().equals(anotherId); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Permission.java ================================================ /* * 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.authorization; import org.hswebframework.web.authorization.access.DataAccessConfig; import org.hswebframework.web.authorization.access.FieldFilterDataAccessConfig; import org.hswebframework.web.authorization.access.ScopeDataAccessConfig; import java.io.Serializable; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import static org.hswebframework.web.authorization.access.DataAccessConfig.DefaultType.DENY_FIELDS; /** * 用户持有的权限信息,包含了权限基本信息、可操作范围(action)、行,列级权限控制规则。 * 是用户权限的重要接口。 * * @author zhouhao * @see Authentication * @since 3.0 */ public interface Permission extends Serializable { /** * 查询 */ String ACTION_QUERY = "query"; /** * 获取明细 */ String ACTION_GET = "get"; /** * 新增 */ String ACTION_ADD = "add"; /** * 保存 */ String ACTION_SAVE = "save"; /** * 更新 */ String ACTION_UPDATE = "update"; /** * 删除 */ String ACTION_DELETE = "delete"; /** * 导入 */ String ACTION_IMPORT = "import"; /** * 导出 */ String ACTION_EXPORT = "export"; /** * 禁用 */ String ACTION_DISABLE = "disable"; /** * 启用 */ String ACTION_ENABLE = "enable"; /** * @return 权限ID,权限的唯一标识 */ String getId(); /** * @return 权限名称 */ String getName(); /** * @return 其他拓展字段 */ Map getOptions(); default Optional getOption(String key) { return Optional.ofNullable(getOptions()) .map(map -> map.get(key)); } /** * 用户对此权限的可操作事件(按钮) * * ⚠️:任何时候都不应该对返回的Set进行写操作 * * @return 如果没有配置返回空{@link Collections#emptySet()},不会返回null. */ Set getActions(); /** * 用户对此权限持有的数据权限信息, 用于数据级别的控制 * * ⚠️:任何时候都不应该对返回的Set进行写操作 * * @return 如果没有配置返回空{@link Collections#emptySet()},不会返回null. * @see DataAccessConfig * @see org.hswebframework.web.authorization.access.DataAccessController */ @Deprecated Set getDataAccesses(); default Set getDataAccesses(String action) { return getDataAccesses() .stream() .filter(conf -> conf.getAction().equals(action)) .collect(Collectors.toSet()); } /** * 查找数据权限配置 * * @param configPredicate 数据权限配置匹配规则 * @param 数据权限配置类型 * @return {@link Optional} * @see this#scope(String, String, String) */ @SuppressWarnings("all") default Optional findDataAccess(DataAccessPredicate configPredicate) { return (Optional) getDataAccesses().stream() .filter(configPredicate) .findFirst(); } /** * 查找字段过滤的数据权限配置(列级数据权限),比如:不查询某些字段 * * @param action 权限操作类型 {@link Permission#ACTION_QUERY} * @return {@link Optional} * @see FieldFilterDataAccessConfig * @see FieldFilterDataAccessConfig#getFields() */ default Optional findFieldFilter(String action) { return findDataAccess(conf -> conf instanceof FieldFilterDataAccessConfig && conf.getAction().equals(action)); } /** * 获取不能执行操作的字段 * * @param action 权限操作 * @return 未配置时返回空set, 不会返回null */ default Set findDenyFields(String action) { return findFieldFilter(action) .filter(conf -> DENY_FIELDS.equals(conf.getType().getId())) .map(FieldFilterDataAccessConfig::getFields) .orElseGet(Collections::emptySet); } /** * 查找数据范围权限控制配置(行级数据权限),比如: 只能查询本机构的数据 * * @param type 范围类型标识,由具体的实现定义,如: 机构范围 * @param scopeType 范围类型,由具体的实现定义,如: 只能查看自己所在机构 * @param action 权限操作 {@link Permission#ACTION_QUERY} * @return 未配置时返回空set, 不会返回null */ default Set findScope(String action, String type, String scopeType) { return findScope(scope(action, type, scopeType)); } default Set findScope(Permission.DataAccessPredicate predicate) { return findDataAccess(predicate) .map(ScopeDataAccessConfig::getScope) .orElseGet(Collections::emptySet); } /** * 构造一个数据范围权限控制配置查找逻辑 * * @param type 范围类型标识,由具体的实现定义,如: 机构范围 * @param scopeType 范围类型,由具体的实现定义,如: 只能查看自己所在机构 * @param action 权限操作 {@link Permission#ACTION_QUERY} * @return {@link DataAccessPredicate} */ static Permission.DataAccessPredicate scope(String action, String type, String scopeType) { Objects.requireNonNull(action, "action can not be null"); Objects.requireNonNull(type, "type can not be null"); Objects.requireNonNull(scopeType, "scopeType can not be null"); return config -> config instanceof ScopeDataAccessConfig && action.equals(config.getAction()) && type.equals(config.getType()) && scopeType.equals(((ScopeDataAccessConfig) config).getScopeType()); } Permission copy(); Permission copy(Predicate actionFilter,Predicate dataAccessFilter); /** * 数据权限查找判断逻辑接口 * * @param */ interface DataAccessPredicate extends Predicate { boolean test(DataAccessConfig config); @Override default DataAccessPredicate and(Predicate super DataAccessConfig> other) { return (t) -> test(t) && other.test(t); } @Override default DataAccessPredicate or(Predicate super DataAccessConfig> other) { return (t) -> test(t) || other.test(t); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationHolder.java ================================================ /* * Copyright 2019 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.authorization; import com.google.common.collect.Lists; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.context.Context; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; /** * 响应式权限保持器,用于响应式方式获取当前登录用户的权限信息. * 例如: * {@code * @RequestMapping("/example") * public Mono example(){ * return ReactiveAuthenticationHolder.get(); * } * } * * * @author zhouhao * @see ReactiveAuthenticationSupplier * @since 4.0 */ public final class ReactiveAuthenticationHolder { private static final List suppliers = new CopyOnWriteArrayList<>(); public static final String IGNORE_AUTH_KEY = ".auth.ignore"; static final Context IGNORE_AUTH_CONTEXT_Y = Context.of(IGNORE_AUTH_KEY, true); static final Context IGNORE_AUTH_CONTEXT_N = Context.of(IGNORE_AUTH_KEY, false); private static Mono get(Function> function) { return AuthenticationUtils .merge(Flux.merge(Lists.transform(suppliers, function::apply))); } /** * @return 当前登录的用户权限信息 */ public static Mono get() { return Mono.deferContextual(ctx -> { if (Boolean.TRUE.equals(ctx.getOrDefault(IGNORE_AUTH_KEY, false))) { return Mono.empty(); } return get(ReactiveAuthenticationSupplier::get); }); } /** * 获取指定用户的权限信息 * * @param userId 用户ID * @return 权限信息 */ public static Mono get(String userId) { return get(supplier -> supplier.get(userId)); } /** * 初始化 {@link ReactiveAuthenticationSupplier} * * @param supplier */ public static void addSupplier(ReactiveAuthenticationSupplier supplier) { suppliers.add(supplier); } public static void setSupplier(ReactiveAuthenticationSupplier supplier) { suppliers.clear(); suppliers.add(supplier); } public static Context ignoreContext(boolean ignore) { return ignore ? IGNORE_AUTH_CONTEXT_Y : IGNORE_AUTH_CONTEXT_N; } public static Function ignoreIfAbsent(boolean ignore) { return ctx -> ctx.hasKey(IGNORE_AUTH_KEY) ? ctx : ctx.put(IGNORE_AUTH_KEY, ignore); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationInitializeService.java ================================================ /* * 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.authorization; import org.hswebframework.web.authorization.events.AuthorizationInitializeEvent; import reactor.core.publisher.Mono; /** * 授权信息初始化服务接口,使用该接口初始化用的权限信息 * * @author zhouhao * @since 4.0 */ public interface ReactiveAuthenticationInitializeService { /** * 根据用户ID初始化权限信息 * * @param userId 用户ID * @return 权限信息 * @see AuthorizationInitializeEvent */ Mono initUserAuthorization(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationManager.java ================================================ /* * 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.authorization; import reactor.core.publisher.Mono; /** * 授权信息管理器,用于获取用户授权和同步授权信息 * * @author zhouhao * @see 3.0 */ public interface ReactiveAuthenticationManager { /** * 进行授权操作 * * @param request 授权请求 * @return 授权成功则返回用户权限信息 */ Mono authenticate(Mono request); /** * 根据用户ID获取权限信息 * * @param userId 用户ID * @return 权限信息 */ Mono getByUserId(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationManagerProvider.java ================================================ package org.hswebframework.web.authorization; import reactor.core.publisher.Mono; public interface ReactiveAuthenticationManagerProvider { /** * 进行授权操作 * * @param request 授权请求 * @return 授权成功则返回用户权限信息 */ Mono authenticate(Mono request); /** * 根据用户ID获取权限信息 * * @param userId 用户ID * @return 权限信息 */ Mono getByUserId(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationSupplier.java ================================================ /* * 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.authorization; import reactor.core.publisher.Mono; import java.util.function.Supplier; /** * @author zhouhao * @see Supplier * @see Authentication * @see ReactiveAuthenticationHolder * @since 4.0 */ public interface ReactiveAuthenticationSupplier extends Supplier> { Mono get(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Role.java ================================================ /* * 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.authorization; import org.hswebframework.web.authorization.simple.SimpleRole; /** * 角色信息 * * @author zhouhao * @since 3.0 */ public interface Role extends Dimension { /** * @return 角色ID */ String getId(); /** * @return 角色名 */ String getName(); @Override default DimensionType getType() { return DefaultDimensionType.role; } static Role fromDimension(Dimension dimension){ return SimpleRole.of(dimension); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/User.java ================================================ /* * 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.authorization; /** * 用户信息 * * @author zhouhao * @since 3.0 */ public interface User extends Dimension { /** * @return 用户ID */ String getId(); /** * @return 用户名 */ String getUsername(); /** * @return 姓名 */ String getName(); /** * @return 用户类型 */ String getUserType(); @Override default DimensionType getType() { return DefaultDimensionType.user; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessConfig.java ================================================ /* * 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.authorization.access; import org.hswebframework.web.authorization.Permission; import java.io.Serializable; /** * 数据级的权限控制,此接口为控制方式配置 * 具体的控制逻辑由控制器{@link DataAccessController}实现 * * @author zhouhao * @see OwnCreatedDataAccessConfig */ public interface DataAccessConfig extends Serializable { /** * 对数据的操作事件 * * @return 操作时间 * @see Permission#ACTION_ADD * @see Permission#ACTION_DELETE * @see Permission#ACTION_GET * @see Permission#ACTION_QUERY * @see Permission#ACTION_UPDATE */ String getAction(); /** * 控制方式标识 * * @return 控制方式 * @see DefaultType */ DataAccessType getType(); /** * 内置的控制方式 */ interface DefaultType { /** * 自己创建的数据 * * @see OwnCreatedDataAccessConfig#getType() */ String OWN_CREATED = "OWN_CREATED"; /** * 禁止操作字段 * * @see FieldFilterDataAccessConfig#getType() */ String DENY_FIELDS = "DENY_FIELDS"; /** * 禁止操作字段 * * @see org.hswebframework.web.authorization.simple.DimensionDataAccessConfig#getType() */ String DIMENSION_SCOPE = "DIMENSION_SCOPE"; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessConfiguration.java ================================================ package org.hswebframework.web.authorization.access; public interface DataAccessConfiguration { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessController.java ================================================ package org.hswebframework.web.authorization.access; import org.hswebframework.web.authorization.define.AuthorizingContext; /** * 数据级别权限控制器,通过此控制器对当前登录用户进行的操作进行数据级别的权限控制。 * 如:A用户只能查询自己创建的B数据,A用户只能修改自己创建的B数据 * * @author zhouhao * @since 3.0 */ @Deprecated public interface DataAccessController { /** * 执行权限控制 * @param access 控制方式以及配置 * @param context 权限验证上下文,用于传递验证过程用到的参数 * @return 授权是否通过 */ boolean doAccess(DataAccessConfig access, AuthorizingContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessHandler.java ================================================ package org.hswebframework.web.authorization.access; import org.hswebframework.web.authorization.define.AuthorizingContext; /** * 数据级别权限控制处理器接口,负责处理支持的权限控制配置 * * @author zhouhao */ public interface DataAccessHandler { /** * 是否支持处理此配置 * * @param access 控制配置 * @return 是否支持 */ boolean isSupport(DataAccessConfig access); /** * 执行处理,返回处理结果 * * @param access 控制配置 * @param context 参数上下文 * @return 处理结果 */ boolean handle(DataAccessConfig access, AuthorizingContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessType.java ================================================ package org.hswebframework.web.authorization.access; public interface DataAccessType { String getId(); String getName(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DefaultDataAccessType.java ================================================ package org.hswebframework.web.authorization.access; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.web.dict.Dict; import org.hswebframework.web.dict.EnumDict; @Getter @AllArgsConstructor public enum DefaultDataAccessType implements DataAccessType, EnumDict { USER_OWN_DATA("自己的数据"), FIELD_DENY("禁止操作字段"), DIMENSION_SCOPE("维度范围"); private final String name; @Override public String getText() { return name; } @Override public String getValue() { return getId(); } @Override public String getId() { return name().toLowerCase(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DimensionHelper.java ================================================ package org.hswebframework.web.authorization.access; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.simple.DimensionDataAccessConfig; import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; @NoArgsConstructor(access = AccessLevel.PRIVATE) public abstract class DimensionHelper { public static Set getDimensionDataAccessScope(Authentication atz, Permission permission, String action, String dimensionType) { return permission .getDataAccesses(action) .stream() .filter(DimensionDataAccessConfig.class::isInstance) .map(DimensionDataAccessConfig.class::cast) .filter(conf -> dimensionType.equals(conf.getScopeType())) .flatMap(conf -> { if (CollectionUtils.isEmpty(conf.getScope())) { return atz.getDimensions(dimensionType) .stream() .map(Dimension::getId); } return conf.getScope().stream(); }).collect(Collectors.toSet()); } public static Set getDimensionDataAccessScope(Authentication atz, Permission permission, String action, DimensionType dimensionType) { return getDimensionDataAccessScope(atz, permission, action, dimensionType.getId()); } public static Set getDimensionDataAccessScope(Authentication atz, String permission, String action, String dimensionType) { return atz .getPermission(permission) .map(per -> getDimensionDataAccessScope(atz, per, action, dimensionType)).orElseGet(Collections::emptySet); } public static Set getDimensionDataAccessScope(Authentication atz, String permission, String action, DimensionType dimensionType) { return atz .getPermission(permission) .map(per -> getDimensionDataAccessScope(atz, per, action, dimensionType)) .orElseGet(Collections::emptySet); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/FieldFilterDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.access; import java.util.Set; /** * 对字段进行过滤操作配置,实现字段级别的权限控制 * * @author zhouhao * @see DataAccessConfig * @see org.hswebframework.web.authorization.simple.SimpleFieldFilterDataAccessConfig */ public interface FieldFilterDataAccessConfig extends DataAccessConfig { Set getFields(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/OwnCreatedDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.access; /** * 只能操作由自己创建的数据 * * @author zhouhao */ public interface OwnCreatedDataAccessConfig extends DataAccessConfig { @Override default DataAccessType getType() { return DefaultDataAccessType.USER_OWN_DATA; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/ScopeDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.access; import java.util.Set; /** * 范围数据权限控制配置 * * @author zhouhao * @see DataAccessConfig * @since 3.0 */ public interface ScopeDataAccessConfig extends DataAccessConfig { /** * @return 范围类型 */ String getScopeType(); /** * @return 自定义的控制范围 */ Set getScope(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/UserAttachEntity.java ================================================ package org.hswebframework.web.authorization.access; /** * 和user关联的实体 * * @author zhouhao * @since 3.0.6 */ public interface UserAttachEntity { String userId = "userId"; String getUserId(); void setUserId(String userId); default String getUserIdProperty() { return userId; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java ================================================ /* * * * 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.authorization.annotation; import org.hswebframework.web.authorization.define.Phased; import java.lang.annotation.*; /** * 基础权限控制注解,提供基本的控制配置 * * @author zhouhao * @see org.hswebframework.web.authorization.Authentication * @see org.hswebframework.web.authorization.define.AuthorizeDefinition * @see Resource * @see ResourceAction * @see Dimension * @see DataAccess * @since 3.0 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Authorize { Resource[] resources() default {}; Dimension[] dimension() default {}; /** * 是否运行匿名访问,匿名访问时,直接允许执行,否则将进行权限验证. * * @return 是否允许匿名访问 * @since 4.0.19 */ boolean anonymous() default false; /** * 验证失败时返回的消息 * * @return 验证失败提示的消息 */ String message() default "无访问权限"; /** * 是否合并类上的注解 * * @return 是否合并类上的注解 */ boolean merge() default true; /** * 验证模式,在使用多个验证条件时有效 * * @return logical */ Logical logical() default Logical.DEFAULT; /** * @return 验证时机,在方法调用前还是调用后 */ Phased phased() default Phased.before; /** * @return 是否忽略, 忽略后将不进行权限控制 */ boolean ignore() default false; String[] description() default {}; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/CreateAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_ADD, name = "新增") public @interface CreateAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccess.java ================================================ /* * 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.authorization.annotation; import org.hswebframework.web.authorization.access.DataAccessController; import java.lang.annotation.*; /** * 数据级权限控制注解,用于进行需要数据级别权限控制的声明. * * 此注解仅用于声明此方法需要进行数据级权限控制,具体权限控制方式由控制器实{@link DataAccessController}现 * * * @author zhouhao * @see DataAccessController * @see ResourceAction#dataAccess() * @since 3.0 * @deprecated 已弃用, 4.1中移除 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Deprecated public @interface DataAccess { DataAccessType[] type() default {}; /** * @return logical */ Logical logical() default Logical.AND; /** * @return 是否忽略, 忽略后将不进行权限控制 */ boolean ignore() default false; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccessType.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.access.DataAccessConfiguration; import org.hswebframework.web.authorization.access.DataAccessController; import java.lang.annotation.*; @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Deprecated public @interface DataAccessType { String id(); //标识 String name(); //名称 String[] description() default {}; /** * @see DataAccessController */ Class extends DataAccessController> controller() default DataAccessController.class; Class extends DataAccessConfiguration> configuration() default DataAccessConfiguration.class; boolean ignore() default false; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DeleteAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_DELETE, name = "删除") public @interface DeleteAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Dimension.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.DimensionType; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 请使用注解继承方式使用此注解 * * @author zhouhao * @see RequiresRoles * @since 4.0 */ @Target({ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(value = Dimension.List.class) public @interface Dimension { /** * 维度类型标识,如: role,org * * @return 维度类型 * @see org.hswebframework.web.authorization.Dimension#getType() * @see DimensionType#getId() * @see org.hswebframework.web.authorization.Authentication#hasDimension(String, String...) */ String type(); /** * 具体的维度ID,如: 角色ID,组织ID * * @return 维度ID * @see org.hswebframework.web.authorization.Dimension#getId() * @see org.hswebframework.web.authorization.Authentication#hasDimension(String, String...) */ String[] id() default {}; /** * 配置了多个ID时的判断逻辑,默认为任意满足则认为有权限. * * @return Logical */ Logical logical() default Logical.DEFAULT; /** * @return 说明 */ String[] description() default {}; /** * @return 是否忽略 */ boolean ignore() default false; @Target({ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @Inherited @interface List { Dimension[] value() default {}; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DimensionDataAccess.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.define.Phased; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @DataAccessType(id = "dimension", name = "维度数据权限") @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Authorize @Deprecated public @interface DimensionDataAccess { Mapping[] mapping() default {}; @AliasFor(annotation = Authorize.class) Phased phased() default Phased.before; @AliasFor(annotation = DataAccessType.class) boolean ignore() default false; @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @interface Mapping { String dimensionType(); String property(); int idParamIndex() default -1; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Dimensions.java ================================================ package org.hswebframework.web.authorization.annotation; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 标记多个维度的权限控制相关配置 * * @author zhouhao * @since 5.0.1 */ @Target({ElementType.METHOD, TYPE, ANNOTATION_TYPE, FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Dimensions { /** * 存在多个维度时的判断逻辑,默认任意一个满足则认为有权限 * * @return Logical */ Logical logical() default Logical.DEFAULT; /** * @return 针对当前配置的说明信息 */ String[] description() default {}; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/FieldDataAccess.java ================================================ package org.hswebframework.web.authorization.annotation; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; /** * @deprecated 已弃用 */ @DataAccessType(id = "FIELD_DENY", name = "字段权限") @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Deprecated public @interface FieldDataAccess { @AliasFor(annotation = DataAccessType.class) boolean ignore() default false; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Logical.java ================================================ /* * 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.authorization.annotation; public enum Logical { AND, OR, DEFAULT } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/QueryAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_QUERY, name = "查询") public @interface QueryAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/RequiresRoles.java ================================================ package org.hswebframework.web.authorization.annotation; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 注解根据角色维度进行权限控制,具有权限的用户才可访问对应的方法. * * {@code * @RequiresRoles("admin") * public Mono handleRequest(){ * * } * } * * @author zhouhao * @see Dimension * @since 4.0 */ @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Dimension(type = "role") @Repeatable(RequiresRoles.List.class) public @interface RequiresRoles { /** * @return 角色ID */ @AliasFor(annotation = Dimension.class, attribute = "id") String[] value() default {}; /** * 多个角色时的判断逻辑 * @return Logical */ @AliasFor(annotation = Dimension.class, attribute = "logical") Logical logical() default Logical.DEFAULT; @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) @Retention(RUNTIME) @Documented @Inherited @Dimension.List() @interface List { RequiresRoles[] value(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Resource.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.define.Phased; import java.lang.annotation.*; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 接口资源声明注解,声明Controller的资源相关信息,用于进行权限控制。 * * 在Controller进行注解,表示此接口需要有对应的权限{@link Permission#getId()}才能进行访问. * 具体的操作权限控制,需要在方法上注解{@link ResourceAction}. * * * * {@code * @RestController * //声明资源 * @Resource(id = "test", name = "测试功能") * public class TestController implements ReactiveCrudController { * * //声明操作,需要有 test:query 权限才能访问此接口 * @QueryAction * public Mono getUser() { * return Authentication.currentReactive() * .switchIfEmpty(Mono.error(new UnAuthorizedException())) * .map(Authentication::getUser); * } * * } * } * * 如果接口不需要进行权限控制,可注解{@link Authorize#ignore()}来标识此接口不需要权限控制. * 或者通过监听 {@link org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent}来进行自定义处理 * {@code * @EventListener * public void handleAuthEvent(AuthorizingHandleBeforeEvent e) { * //admin用户可以访问全部操作 * if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) { * e.setAllow(true); * } * } * } * * @author zhouhao * @see ResourceAction * @see Authorize * @see org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent * @since 4.0 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(Resource.List.class) public @interface Resource { /** * 资源ID * * @return 资源ID */ String id(); /** * @return 资源名称 */ String name(); /** * @return 资源操作定义 */ ResourceAction[] actions() default {}; /** * @return 多个操作控制逻辑 */ Logical logical() default Logical.DEFAULT; /** * @return 权限控制阶段 */ Phased phased() default Phased.before; /** * @return 资源描述 */ String[] description() default {}; /** * @return 资源分组 */ String[] group() default {}; /** * 如果在方法上设置此属性,表示是否合并类上注解的属性 * * @return 是否合并 */ boolean merge() default true; @Target({ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @Inherited @interface List { Resource[] value(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/ResourceAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 对资源操作的描述,通常用来进行权限控制. * * 在Controller方法上添加此注解,来声明根据权限操作{@link Permission#getActions()}进行权限控制. * * 可以使用注解继承的方式来统一定义操作: * {@code * @Target(ElementType.METHOD) * @Retention(RetentionPolicy.RUNTIME) * @Inherited * @Documented * @ResourceAction(id = "create", name = "新增") * public @interface CreateAction { * * } * } * * * @see CreateAction * @see DeleteAction * @see SaveAction * @see org.hswebframework.web.authorization.Authentication * @see Permission#getActions() */ @Target({ANNOTATION_TYPE, FIELD, METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(ResourceAction.List.class) public @interface ResourceAction { /** * 操作标识 * * @return 操作标识 * @see Permission#getActions() */ String id(); /** * @return 操作名称 */ String name(); /** * @return 操作说明 */ String[] description() default {}; /** * @return 多个操作时的判断逻辑 */ Logical logical() default Logical.DEFAULT; @Target({ANNOTATION_TYPE, FIELD, METHOD}) @Retention(RUNTIME) @Documented @Inherited @interface List { ResourceAction[] value(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/SaveAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import java.lang.annotation.*; /** * 继承{@link ResourceAction},提供统一的id定义 * * @author zhouhao * @since 4.0 */ @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_SAVE, name = "保存") public @interface SaveAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/TwoFactor.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.twofactor.TwoFactorValidator; import java.lang.annotation.*; /** * 开启2FA双重验证 * * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidator * @since 3.0.4 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface TwoFactor { /** * @return 接口的标识, 用于实现不同的操作, 可能会配置不同的验证规则 */ String value(); /** * @return 验证有效期, 超过有效期后需要重新进行验证 */ long timeout() default 10 * 60 * 1000L; /** * 验证器供应商,如: totp,sms,email,由{@link org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider}进行自定义. * * 可通过配置项: hsweb.authorize.two-factor.default-provider 来修改默认配置 * * @return provider * @see TwoFactorValidator#getProvider() */ String provider() default "default"; /** * 验证码的http参数名,在进行验证的时候需要传入此参数 * * @return 验证码的参数名 */ String parameter() default "verifyCode"; /** * @return 关闭验证 */ boolean ignore() default false; /** * * @return 错误提示 * @since 3.0.6 */ String message() default "validation.verify_code_error"; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/UserOwnData.java ================================================ package org.hswebframework.web.authorization.annotation; import java.lang.annotation.*; /** * 声明某个操作支持用户查看自己的数据 * * @deprecated 已弃用 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @DataAccessType(id = "user_own_data", name = "用户自己的数据") @Deprecated public @interface UserOwnData { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/AuthenticationBuilder.java ================================================ /* * 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.authorization.builder; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.Role; import org.hswebframework.web.authorization.User; import java.io.Serializable; import java.util.List; import java.util.Map; public interface AuthenticationBuilder extends Serializable { AuthenticationBuilder user(User user); AuthenticationBuilder user(String user); AuthenticationBuilder user(Map user); AuthenticationBuilder role(List role); AuthenticationBuilder role(String role); AuthenticationBuilder permission(List permission); AuthenticationBuilder permission(String permission); AuthenticationBuilder attributes(String attributes); AuthenticationBuilder attributes(Map permission); AuthenticationBuilder json(String json); Authentication build(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/AuthenticationBuilderFactory.java ================================================ package org.hswebframework.web.authorization.builder; /** * 权限构造器工厂 * * @author zhouhao */ public interface AuthenticationBuilderFactory { /** * @return 新建一个权限构造器 */ AuthenticationBuilder create(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/DataAccessConfigBuilder.java ================================================ package org.hswebframework.web.authorization.builder; import org.hswebframework.web.authorization.access.DataAccessConfig; import java.util.Map; /** * * @author zhouhao */ public interface DataAccessConfigBuilder { DataAccessConfigBuilder fromJson(String json); DataAccessConfigBuilder fromMap(Map json); DataAccessConfig build(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/DataAccessConfigBuilderFactory.java ================================================ package org.hswebframework.web.authorization.builder; /** * 数据权限配置构造器工厂 * * @author zhouhao */ public interface DataAccessConfigBuilderFactory { /** * @return 新建一个数据权限配置构造器工厂 */ DataAccessConfigBuilder create(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/context/AuthenticationThreadLocalAccessor.java ================================================ package org.hswebframework.web.authorization.context; import io.micrometer.context.ThreadLocalAccessor; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationHolder; import org.hswebframework.web.authorization.ReactiveAuthenticationHolder; import javax.annotation.Nonnull; public class AuthenticationThreadLocalAccessor implements ThreadLocalAccessor { static final Object KEY = Authentication.class; static { ReactiveAuthenticationHolder.addSupplier( new ThreadLocalReactiveAuthenticationSupplier() ); } @Override @Nonnull public Object key() { return KEY; } @Override public Authentication getValue() { return AuthenticationHolder.get().orElse(null); } @Override public void setValue() { AuthenticationHolder.resetCurrent(); } @Override public void setValue(@Nonnull Authentication value) { AuthenticationHolder.makeCurrent(value); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/context/ThreadLocalReactiveAuthenticationSupplier.java ================================================ package org.hswebframework.web.authorization.context; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationHolder; import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier; import reactor.core.publisher.Mono; class ThreadLocalReactiveAuthenticationSupplier implements ReactiveAuthenticationSupplier { @Override public Mono get(String userId) { return Mono.empty(); } @Override public Mono get() { return Mono.justOrEmpty(AuthenticationHolder.get()); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AopAuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.lang.reflect.Method; /** * @author zhouhao * @since 1.0 */ public interface AopAuthorizeDefinition extends AuthorizeDefinition { Class> getTargetClass(); Method getTargetMethod(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.util.StringJoiner; /** * 权限控制定义,定义权限控制的方式 * * @author zhouhao * @since 3.0 */ public interface AuthorizeDefinition { ResourcesDefinition getResources(); DimensionsDefinition getDimensions(); String getMessage(); Phased getPhased(); boolean isEmpty(); default boolean allowAnonymous() { return false; } default String getDescription() { ResourcesDefinition res = getResources(); StringJoiner joiner = new StringJoiner(";"); for (ResourceDefinition resource : res.getResources()) { joiner.add(resource.getId() + ":" + String.join(",", resource.getActionIds())); } return joiner.toString(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionContext.java ================================================ package org.hswebframework.web.authorization.define; public interface AuthorizeDefinitionContext { void addResource(ResourceDefinition def); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionCustomizer.java ================================================ package org.hswebframework.web.authorization.define; public interface AuthorizeDefinitionCustomizer { void custom(AuthorizeDefinitionContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionInitializedEvent.java ================================================ package org.hswebframework.web.authorization.define; import org.hswebframework.web.authorization.events.AuthorizationEvent; import org.springframework.context.ApplicationEvent; import java.util.List; public class AuthorizeDefinitionInitializedEvent extends ApplicationEvent implements AuthorizationEvent { private static final long serialVersionUID = -8185138454949381441L; public AuthorizeDefinitionInitializedEvent(List all) { super(all); } @SuppressWarnings("unchecked") public List getAllDefinition() { return ((List) getSource()); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizingContext.java ================================================ package org.hswebframework.web.authorization.define; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.aop.MethodInterceptorContext; import org.hswebframework.web.authorization.Authentication; /** * 权限控制上下文 */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class AuthorizingContext { private AuthorizeDefinition definition; private Authentication authentication; private MethodInterceptorContext paramContext; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/CompositeAuthorizeDefinitionCustomizer.java ================================================ package org.hswebframework.web.authorization.define; import lombok.AllArgsConstructor; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @AllArgsConstructor public class CompositeAuthorizeDefinitionCustomizer implements AuthorizeDefinitionCustomizer{ private final List customizers; public CompositeAuthorizeDefinitionCustomizer(Iterable customizers){ this(StreamSupport.stream(customizers.spliterator(),false).collect(Collectors.toList())); } @Override public void custom(AuthorizeDefinitionContext context) { for (AuthorizeDefinitionCustomizer customizer : customizers) { customizer.custom(context); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DataAccessDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.Getter; import lombok.Setter; import java.util.*; @Getter @Setter public class DataAccessDefinition { Set dataAccessTypes = new HashSet<>(); public Optional getType(String typeId) { return dataAccessTypes .stream() .filter(type -> type.getId() != null && type.getId().equalsIgnoreCase(typeId)) .findAny(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DataAccessTypeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.access.DataAccessController; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DataAccessConfiguration; import org.hswebframework.web.bean.FastBeanCopier; @Getter @Setter @EqualsAndHashCode(of = "id") public class DataAccessTypeDefinition implements DataAccessType { private String id; private String name; private String description; private Class extends DataAccessController> controller; private Class extends DataAccessConfiguration> configuration; public DataAccessTypeDefinition copy(){ return FastBeanCopier.copy(this,DataAccessTypeDefinition::new); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DimensionDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.annotation.Logical; import org.hswebframework.web.bean.FastBeanCopier; import reactor.function.Predicate3; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; @Getter @Setter @EqualsAndHashCode(of = "typeId") public class DimensionDefinition { private String typeId; private String typeName; private Set dimensionId = new HashSet<>(); private Logical logical = Logical.DEFAULT; public boolean hasDimension(Predicate3> filter) { return filter.test(typeId,logical, Collections.unmodifiableSet(dimensionId)); } public boolean hasDimension(Set dimensionIdPredicate) { if (logical == Logical.AND) { return dimensionIdPredicate.containsAll(dimensionId); } return dimensionId .stream() .anyMatch(dimensionIdPredicate::contains); } public boolean hasDimension(String id) { return dimensionId.contains(id); } public void addDimensionI(Set id) { dimensionId.addAll(id); } public DimensionDefinition copy() { return FastBeanCopier.copy(this, DimensionDefinition::new); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DimensionsDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Predicate; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.annotation.Logical; import reactor.function.Predicate3; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiPredicate; import java.util.stream.Collectors; @Getter @Setter public class DimensionsDefinition { private Map dimensionsMapping = new ConcurrentHashMap<>(); private Logical logical = Logical.DEFAULT; private String description; public Set getDimensions() { return new HashSet<>(dimensionsMapping.values()); } public void clear() { dimensionsMapping.clear(); } public void addDimension(DimensionDefinition definition) { DimensionDefinition old = dimensionsMapping.putIfAbsent(definition.getTypeId(), definition); if (old != null) { old.addDimensionI(definition.getDimensionId()); } } public boolean isEmpty() { return MapUtils.isEmpty(this.dimensionsMapping); } public boolean hasDimension(Dimension dimension) { DimensionDefinition def = dimensionsMapping.get(dimension.getType().getId()); return def != null && def.hasDimension(dimension.getId()); } public boolean hasDimension(Predicate3> filter) { if (logical == Logical.AND) { return dimensionsMapping .values() .stream() .allMatch(e -> e.hasDimension(filter)); } else { return dimensionsMapping .values() .stream() .anyMatch(e -> e.hasDimension(filter)); } } public boolean hasDimension(List dimensions) { if (logical == Logical.AND) { return dimensions.stream().allMatch(this::hasDimension); } return dimensions.stream().anyMatch(this::hasDimension); } @Override public String toString() { return dimensionsMapping .values() .stream() .map(d -> String.join(",", d.getDimensionId()) + "@" + d.getTypeId()) .collect(Collectors.joining(";")); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/HandleType.java ================================================ package org.hswebframework.web.authorization.define; public enum HandleType{ RBAC,DATA } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/MergedAuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.util.List; import java.util.Set; public class MergedAuthorizeDefinition implements AuthorizeDefinitionContext { private final ResourcesDefinition resources = new ResourcesDefinition(); private final DimensionsDefinition dimensions = new DimensionsDefinition(); public Set getResources() { return resources.getResources(); } public Set getDimensions() { return dimensions.getDimensions(); } public void addResource(ResourceDefinition resource) { resources.addResource(resource, true); } public void addDimension(DimensionDefinition resource) { dimensions.addDimension(resource); } public void merge(List definitions) { for (AuthorizeDefinition definition : definitions) { definition.getResources().getResources().forEach(this::addResource); definition.getDimensions().getDimensions().forEach(this::addDimension); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/Phased.java ================================================ package org.hswebframework.web.authorization.define; public enum Phased { before, after } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourceActionDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.bean.FastBeanCopier; import org.hswebframework.web.i18n.I18nSupportUtils; import org.hswebframework.web.i18n.MultipleI18nSupportEntity; import java.util.Collection; import java.util.HashMap; import java.util.Locale; import java.util.Map; import static org.hswebframework.web.authorization.define.ResourceDefinition.supportLocale; @Getter @Setter @EqualsAndHashCode(of = "id") public class ResourceActionDefinition implements MultipleI18nSupportEntity { private String id; private String name; private String description; private Map> i18nMessages; @Deprecated private DataAccessDefinition dataAccess = new DataAccessDefinition(); private final static String resolveActionPrefix = "hswebframework.web.system.action."; public ResourceActionDefinition copy() { return FastBeanCopier.copy(this, ResourceActionDefinition::new); } public Map> getI18nMessages() { if (org.springframework.util.CollectionUtils.isEmpty(i18nMessages)) { this.i18nMessages = I18nSupportUtils .putI18nMessages( resolveActionPrefix + this.id, "name", supportLocale, null, this.i18nMessages ); } return i18nMessages; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourceDefinition.java ================================================ package org.hswebframework.web.authorization.define; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.annotation.Logical; import org.hswebframework.web.bean.FastBeanCopier; import org.hswebframework.web.i18n.I18nSupportUtils; import org.hswebframework.web.i18n.MultipleI18nSupportEntity; import java.util.*; import java.util.stream.Collectors; @Getter @Setter @EqualsAndHashCode(of = "id") public class ResourceDefinition implements MultipleI18nSupportEntity { private String id; private String name; private String description; private Set actions = new HashSet<>(); private List group; private Map> i18nMessages; @Setter(value = AccessLevel.PRIVATE) @JsonIgnore private volatile Set actionIds; private Logical logical = Logical.DEFAULT; private Phased phased = Phased.before; public final static List supportLocale = new ArrayList<>(); static { supportLocale.add(Locale.CHINESE); supportLocale.add(Locale.ENGLISH); } private final static String resolvePermissionPrefix = "hswebframework.web.system.permission."; public static ResourceDefinition of(String id, String name) { ResourceDefinition definition = new ResourceDefinition(); definition.setId(id); definition.setName(name); return definition; } public Map> getI18nMessages() { if (org.springframework.util.CollectionUtils.isEmpty(i18nMessages)) { this.i18nMessages = I18nSupportUtils .putI18nMessages( resolvePermissionPrefix + this.id, "name", supportLocale, null, this.i18nMessages ); } return i18nMessages; } public ResourceDefinition copy() { ResourceDefinition definition = FastBeanCopier.copy(this, ResourceDefinition::new); definition.setActions(actions.stream().map(ResourceActionDefinition::copy).collect(Collectors.toSet())); return definition; } public ResourceDefinition addAction(String id, String name) { ResourceActionDefinition action = new ResourceActionDefinition(); action.setId(id); action.setName(name); return addAction(action); } public synchronized ResourceDefinition addAction(ResourceActionDefinition action) { actionIds = null; actions.add(action); return this; } public Optional getAction(String action) { return actions.stream() .filter(act -> act.getId().equalsIgnoreCase(action)) .findAny(); } public Set getActionIds() { if (actionIds == null) { actionIds = this.actions .stream() .map(ResourceActionDefinition::getId) .collect(Collectors.toSet()); } return actionIds; } @JsonIgnore public List getDataAccessAction() { return actions.stream() .filter(act -> CollectionUtils.isNotEmpty(act.getDataAccess().getDataAccessTypes())) .collect(Collectors.toList()); } public boolean hasDataAccessAction() { return actions.stream() .anyMatch(act -> CollectionUtils.isNotEmpty(act.getDataAccess().getDataAccessTypes())); } public boolean hasAction(Collection actions) { if (CollectionUtils.isEmpty(this.actions)) { return true; } if (CollectionUtils.isEmpty(actions)) { return false; } if (logical == Logical.AND) { return getActionIds().containsAll(actions); } return getActionIds().stream().anyMatch(actions::contains); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourcesDefinition.java ================================================ package org.hswebframework.web.authorization.define; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.annotation.Logical; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @Getter @Setter public class ResourcesDefinition { private final Set resources = ConcurrentHashMap.newKeySet(); private Logical logical = Logical.DEFAULT; private Phased phased = Phased.before; public void clear() { resources.clear(); } public void addResource(ResourceDefinition resource, boolean merge) { ResourceDefinition definition = getResource(resource.getId()).orElse(null); if (definition != null) { if (merge) { resource.getActions() .stream() .map(ResourceActionDefinition::copy) .forEach(definition::addAction); } else { resources.remove(definition); } } resources.add(resource.copy()); } public Optional getResource(String id) { return resources .stream() .filter(resource -> resource.getId().equals(id)) .findAny(); } @JsonIgnore public List getDataAccessResources() { return resources .stream() .filter(ResourceDefinition::hasDataAccessAction) .collect(Collectors.toList()); } public boolean hasPermission(Permission permission) { if (CollectionUtils.isEmpty(resources)) { return true; } return getResource(permission.getId()) .filter(resource -> resource.hasAction(permission.getActions())) .isPresent(); } public boolean isEmpty() { return resources.isEmpty(); } public boolean hasPermission(Authentication authentication) { int size = resources.size(); if (size == 0) { return true; } if (size == 1) { for (ResourceDefinition resource : resources) { if (authentication.hasPermission(resource.getId(), resource.getActionIds())) { return true; } } return false; } if (logical == Logical.AND) { return resources .stream() .allMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds())); } return resources .stream() .anyMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds())); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionManager.java ================================================ package org.hswebframework.web.authorization.dimension; import reactor.core.publisher.Flux; import java.util.Collection; /** * 维度管理器 * * @author zhouhao * @since 4.0.12 */ public interface DimensionManager { /** * 获取用户维度 * * @param userId 用户ID * @return 用户维度信息 */ Flux getUserDimension(Collection userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserBind.java ================================================ package org.hswebframework.web.authorization.dimension; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class DimensionUserBind implements Externalizable { private static final long serialVersionUID = -6849794470754667710L; private String userId; private String dimensionType; private String dimensionId; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(userId); out.writeUTF(dimensionType); out.writeUTF(dimensionId); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { userId = in.readUTF(); dimensionType = in.readUTF(); dimensionId = in.readUTF(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserBindProvider.java ================================================ package org.hswebframework.web.authorization.dimension; import reactor.core.publisher.Flux; import java.util.Collection; public interface DimensionUserBindProvider { Flux getDimensionBindInfo(Collection userIdList); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserDetail.java ================================================ package org.hswebframework.web.authorization.dimension; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.authorization.Dimension; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class DimensionUserDetail implements Serializable { private static final long serialVersionUID = -6849794470754667710L; private String userId; private List dimensions; public DimensionUserDetail merge(DimensionUserDetail detail) { DimensionUserDetail newDetail = new DimensionUserDetail(); newDetail.setUserId(userId); newDetail.setDimensions(new ArrayList<>()); if (null != dimensions) { newDetail.dimensions.addAll(dimensions); } if (null != detail.getDimensions()) { newDetail.dimensions.addAll(detail.getDimensions()); } return newDetail; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AbstractAuthorizationEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.event.DefaultAsyncEvent; import java.util.Optional; import java.util.function.Function; /** * 抽象授权事件,保存事件常用的数据 * * @author zhouhao * @since 3.0 */ public abstract class AbstractAuthorizationEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -3027505108916079214L; protected String username; protected String password; private final transient Function parameterGetter; /** * 所有参数不能为null * * @param username 用户名 * @param password 密码 * @param parameterGetter 参数获取函数,用户获取授权时传入的参数 */ public AbstractAuthorizationEvent(String username, String password, Function parameterGetter) { if (username == null || password == null || parameterGetter == null) { throw new NullPointerException(); } this.username = username; this.password = password; this.parameterGetter = parameterGetter; } @SuppressWarnings("unchecked") public Optional getParameter(String name) { return Optional.ofNullable((T) parameterGetter.apply(name)); } public String getUsername() { return username; } public String getPassword() { return password; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationBeforeEvent.java ================================================ /* * 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.authorization.events; import lombok.Getter; import org.hswebframework.web.authorization.Authentication; import java.util.function.Function; /** * 授权前事件 * * @author zhouhao * @since 3.0 */ @Getter public class AuthorizationBeforeEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = 5948747533500518524L; private String userId; private Authentication authentication; public AuthorizationBeforeEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public void setAuthorized(String userId) { this.userId = userId; } public void setAuthorized(Authentication authentication) { this.authentication = authentication; } public boolean isAuthorized() { return userId != null || authentication != null; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationDecodeEvent.java ================================================ /* * 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.authorization.events; import java.util.function.Function; /** * 在进行授权时的最开始,触发此事件进行用户名密码解码,解码后请调用{@link #setUsername(String)} {@link #setPassword(String)}重新设置用户名密码 * * @author zhouhao * @since 3.0 */ public class AuthorizationDecodeEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = 5418501934490174251L; public AuthorizationDecodeEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public void setUsername(String username) { super.username = username; } public void setPassword(String password) { super.password = password; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationEvent.java ================================================ /* * 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.authorization.events; /** * 授权事件 * * @author zhouhao * @see AuthorizationSuccessEvent * @see AuthorizationFailedEvent * @see AuthorizationBeforeEvent * @see AuthorizationDecodeEvent * @see AuthorizationExitEvent * @see org.springframework.context.ApplicationEvent * @since 3.0 */ public interface AuthorizationEvent { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationExitEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; import org.springframework.context.ApplicationEvent; /** * 退出登录事件 * * @author zhouhao */ public class AuthorizationExitEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -4590245933665047280L; private final Authentication authentication; public AuthorizationExitEvent(Authentication authentication) { this.authentication = authentication; } public Authentication getAuthentication() { return authentication; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationFailedEvent.java ================================================ /* * 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.authorization.events; import java.util.function.Function; /** * 授权失败时触发 * * @author zhouhao */ public class AuthorizationFailedEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = -101792832265740828L; /** * 异常信息 */ private Throwable exception; public AuthorizationFailedEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public Throwable getException() { return exception; } public void setException(Throwable exception) { this.exception = exception; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationInitializeEvent.java ================================================ package org.hswebframework.web.authorization.events; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; @Getter @Setter @AllArgsConstructor public class AuthorizationInitializeEvent extends DefaultAsyncEvent { private Authentication authentication; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationSuccessEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; /** * 授权成功事件,当授权成功时,触发此事件,并传入授权的信息 * * @author zhouhao * @see Authentication * @since 3.0 */ public class AuthorizationSuccessEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -2452116314154155058L; private final Authentication authentication; private final transient Function parameterGetter; private Map result = new HashMap<>(); public AuthorizationSuccessEvent(Authentication authentication, Function parameterGetter) { this.authentication = authentication; this.parameterGetter = parameterGetter; } public Authentication getAuthentication() { return authentication; } @SuppressWarnings("unchecked") public Optional getParameter(String name) { return Optional.ofNullable((T) parameterGetter.apply(name)); } public Map getResult() { return result; } public void setResult(Map result) { this.result = result; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java ================================================ package org.hswebframework.web.authorization.events; import org.hswebframework.web.authorization.define.AuthorizingContext; import org.hswebframework.web.authorization.define.HandleType; import org.hswebframework.web.event.DefaultAsyncEvent; import org.springframework.context.ApplicationEvent; /** * 权限控制事件,在进行权限控制之前会推送此事件,用于自定义权限控制结果: * {@code * @EventListener * public void handleAuthEvent(AuthorizingHandleBeforeEvent e) { * //admin用户可以访问全部操作 * if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) { * e.setAllow(true); * } * } * } * * @author zhouhao * @since 4.0 */ public class AuthorizingHandleBeforeEvent extends DefaultAsyncEvent implements AuthorizationEvent { private boolean allow = false; private boolean execute = true; private String message; private final AuthorizingContext context; /** * @deprecated 数据权限控制已取消,4.1版本后移除 */ @Deprecated private final HandleType handleType; public AuthorizingHandleBeforeEvent(AuthorizingContext context, HandleType handleType) { this.context = context; this.handleType = handleType; } public AuthorizingContext getContext() { return context; } public boolean isExecute() { return execute; } public boolean isAllow() { return allow; } /** * 设置通过当前请求 * * @param allow allow */ public void setAllow(boolean allow) { execute = false; this.allow = allow; } public String getMessage() { return message; } /** * 设置错误提示消息 * * @param message 消息 */ public void setMessage(String message) { this.message = message; } /** * @return 权限控制类型 */ public HandleType getHandleType() { return handleType; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AccessDenyException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; import org.hswebframework.web.exception.I18nSupportException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.Set; /** * 权限验证异常 * * @author zhouhao * @since 3.0 */ @ResponseStatus(HttpStatus.FORBIDDEN) @Getter public class AccessDenyException extends I18nSupportException { private static final long serialVersionUID = -5135300127303801430L; private String code; public AccessDenyException() { this("error.access_denied"); } public AccessDenyException(String message) { super(message); } public AccessDenyException(String permission, Set actions) { super("error.permission_denied", permission, actions); } public AccessDenyException(String message, String code) { this(message, code, null); } public AccessDenyException(String message, Throwable cause) { this(message, "access_denied", cause); } public AccessDenyException(String message, String code, Throwable cause) { super(message, cause, code); this.code = code; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends AccessDenyException { public NoStackTrace() { super(); } public NoStackTrace(String message) { super(message); } public NoStackTrace(String permission, Set actions) { super(permission, actions); } public NoStackTrace(String message, String code) { super(message, code); } public NoStackTrace(String message, Throwable cause) { super(message, cause); } public NoStackTrace(String message, String code, Throwable cause) { super(message, code, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AuthenticationException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; import org.hswebframework.web.exception.I18nSupportException; @Getter public class AuthenticationException extends I18nSupportException { public static String ILLEGAL_PASSWORD = "illegal_password"; public static String USER_DISABLED = "user_disabled"; private final String code; public AuthenticationException(String code) { this(code, "error." + code); } public AuthenticationException(String code, String message) { super(message); this.code = code; } public AuthenticationException(String code, String message, Throwable cause) { super(message, cause); this.code = code; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends AuthenticationException { public NoStackTrace(String code) { super(code); } public NoStackTrace(String code, String message) { super(code, message); } public NoStackTrace(String code, String message, Throwable cause) { super(code, message, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/NeedTwoFactorException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; /** * @author zhouhao * @since 3.0.4 */ @Getter public class NeedTwoFactorException extends RuntimeException { private static final long serialVersionUID = 3655980280834947633L; private String provider; public NeedTwoFactorException(String message, String provider) { super(message); this.provider = provider; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/UnAuthorizedException.java ================================================ /* * * * 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.authorization.exception; import lombok.Getter; import org.hswebframework.web.authorization.token.TokenState; import org.hswebframework.web.exception.I18nSupportException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; /** * 未授权异常 * * @author zhouhao * @since 3.0 */ @Getter @ResponseStatus(HttpStatus.UNAUTHORIZED) public class UnAuthorizedException extends I18nSupportException { private static final long serialVersionUID = 2422918455013900645L; private final TokenState state; public UnAuthorizedException() { this(TokenState.expired); } public UnAuthorizedException(TokenState state) { this(state.getText(), state); } public UnAuthorizedException(String message, TokenState state) { super(message); this.state = state; } public UnAuthorizedException(String message, TokenState state, Throwable cause) { super(message, cause); this.state = state; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends UnAuthorizedException { public NoStackTrace() { super(); } public NoStackTrace(TokenState state) { super(state); } public NoStackTrace(String message, TokenState state) { super(message, state); } public NoStackTrace(String message, TokenState state, Throwable cause) { super(message, state, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingNullValueHolder.java ================================================ package org.hswebframework.web.authorization.setting; import java.util.List; import java.util.Optional; /** * @author zhouhao * @since 1.0.0 */ public class SettingNullValueHolder implements SettingValueHolder { public static final SettingNullValueHolder INSTANCE = new SettingNullValueHolder(); private SettingNullValueHolder() { } @Override public Optional> asList(Class t) { return Optional.empty(); } @Override public Optional as(Class t) { return Optional.empty(); } @Override public Optional asString() { return Optional.empty(); } @Override public Optional asLong() { return Optional.empty(); } @Override public Optional asInt() { return Optional.empty(); } @Override public Optional asDouble() { return Optional.empty(); } @Override public Optional getValue() { return Optional.empty(); } @Override public UserSettingPermission getPermission() { return UserSettingPermission.NONE; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingValueHolder.java ================================================ package org.hswebframework.web.authorization.setting; import java.util.List; import java.util.Optional; public interface SettingValueHolder { SettingValueHolder NULL = SettingNullValueHolder.INSTANCE; Optional> asList(Class t); Optional as(Class t); Optional asString(); Optional asLong(); Optional asInt(); Optional asDouble(); Optional getValue(); UserSettingPermission getPermission(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/StringSourceSettingHolder.java ================================================ package org.hswebframework.web.authorization.setting; import com.alibaba.fastjson.JSON; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.utils.StringUtils; import org.hswebframework.web.dict.EnumDict; import java.util.List; import java.util.Optional; /** * @author zhouhao * @since 3.0.4 */ @AllArgsConstructor @Getter public class StringSourceSettingHolder implements SettingValueHolder { private String value; private UserSettingPermission permission; public static SettingValueHolder of(String value, UserSettingPermission permission) { if (value == null) { return SettingValueHolder.NULL; } return new StringSourceSettingHolder(value, permission); } @Override public Optional> asList(Class t) { return getNativeValue() .map(v -> JSON.parseArray(v, t)); } protected T convert(String value, Class t) { if (t.isEnum()) { if (EnumDict.class.isAssignableFrom(t)) { T val = (T) EnumDict.find((Class) t, value).orElse(null); if (null != val) { return val; } } for (T enumConstant : t.getEnumConstants()) { if (((Enum) enumConstant).name().equalsIgnoreCase(value)) { return enumConstant; } } } return JSON.parseObject(value, t); } @Override @SuppressWarnings("all") public Optional as(Class t) { if (t == String.class) { return (Optional) asString(); } else if (Long.class == t || long.class == t) { return (Optional) asLong(); } else if (Integer.class == t || int.class == t) { return (Optional) asInt(); } else if (Double.class == t || double.class == t) { return (Optional) asDouble(); } return getNativeValue().map(v -> convert(v, t)); } @Override public Optional asString() { return getNativeValue(); } @Override public Optional asLong() { return getNativeValue().map(StringUtils::toLong); } @Override public Optional asInt() { return getNativeValue().map(StringUtils::toInt); } @Override public Optional asDouble() { return getNativeValue().map(StringUtils::toDouble); } private Optional getNativeValue() { return Optional.ofNullable(value); } @Override public Optional getValue() { return Optional.ofNullable(value); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingManager.java ================================================ package org.hswebframework.web.authorization.setting; /** * @author zhouhao * @since 3.0.4 */ public interface UserSettingManager { SettingValueHolder getSetting(String userId, String key); void saveSetting(String userId, String key, String value, UserSettingPermission permission); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingPermission.java ================================================ package org.hswebframework.web.authorization.setting; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.web.dict.Dict; import org.hswebframework.web.dict.EnumDict; /** * @author zhouhao * @since 3.0.4 */ @AllArgsConstructor @Getter @Dict("user-setting-permission") public enum UserSettingPermission implements EnumDict { NONE("无"), R("读"), W("写"), RW("读写"); private String text; @Override public String getValue() { return name(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/AbstractDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.DataAccessConfig; /** * @author zhouhao * @see DataAccessConfig * @since 3.0 */ public abstract class AbstractDataAccessConfig implements DataAccessConfig { private static final long serialVersionUID = -9025349704771557106L; private String action; @Override public String getAction() { return action; } public void setAction(String action) { this.action = action; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/CompositeReactiveAuthenticationManager.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hswebframework.web.authorization.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; @AllArgsConstructor @Slf4j public class CompositeReactiveAuthenticationManager implements ReactiveAuthenticationManager { private final List providers; @Override public Mono authenticate(Mono request) { return Flux .concat( providers .stream() .map(manager -> manager .authenticate(request) .onErrorResume((err) -> { log.warn("get user authenticate error", err); return Mono.empty(); })) .collect(Collectors.toList())) .take(1) .next(); } @Override public Mono getByUserId(String userId) { if (providers.size() == 1) { return providers.get(0).getByUserId(userId); } return Flux .fromStream(providers .stream() .map(manager -> manager .getByUserId(userId) .onErrorResume((err) -> { log.warn("get user [{}] authentication error", userId, err); return Mono.empty(); }) )) .flatMap(Function.identity()) .as(AuthenticationUtils::merge); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.*; import org.hswebframework.web.authorization.builder.AuthenticationBuilderFactory; import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory; import org.hswebframework.web.authorization.dimension.DimensionManager; import org.hswebframework.web.authorization.dimension.DimensionUserBindProvider; 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.*; import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager; import org.hswebframework.web.authorization.twofactor.defaults.DefaultTwoFactorValidatorManager; import org.hswebframework.web.convert.CustomMessageConverter; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; /** * @author zhouhao */ @AutoConfiguration public class DefaultAuthorizationAutoConfiguration { @Bean @ConditionalOnMissingBean(UserTokenManager.class) @ConfigurationProperties(prefix = "hsweb.user-token") public UserTokenManager userTokenManager() { return new DefaultUserTokenManager(); } @Bean @ConditionalOnMissingBean // @ConditionalOnBean(ReactiveAuthenticationManagerProvider.class) public ReactiveAuthenticationManager reactiveAuthenticationManager(List providers) { return new CompositeReactiveAuthenticationManager(providers); } @Bean @ConditionalOnBean(ReactiveAuthenticationManager.class) public UserTokenReactiveAuthenticationSupplier userTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager, ReactiveAuthenticationManager authenticationManager) { UserTokenReactiveAuthenticationSupplier supplier = new UserTokenReactiveAuthenticationSupplier(userTokenManager, authenticationManager); ReactiveAuthenticationHolder.addSupplier(supplier); return supplier; } @Bean @ConditionalOnBean(AuthenticationManager.class) public UserTokenAuthenticationSupplier userTokenAuthenticationSupplier(UserTokenManager userTokenManager, AuthenticationManager authenticationManager) { UserTokenAuthenticationSupplier supplier = new UserTokenAuthenticationSupplier(userTokenManager, authenticationManager); AuthenticationHolder.addSupplier(supplier); return supplier; } @Bean @ConditionalOnMissingBean(DataAccessConfigBuilderFactory.class) @ConfigurationProperties(prefix = "hsweb.authorization.data-access", ignoreInvalidFields = true) public SimpleDataAccessConfigBuilderFactory dataAccessConfigBuilderFactory() { return new SimpleDataAccessConfigBuilderFactory(); } @Bean @ConditionalOnMissingBean(AuthenticationBuilderFactory.class) public AuthenticationBuilderFactory authenticationBuilderFactory(DataAccessConfigBuilderFactory dataAccessConfigBuilderFactory) { return new SimpleAuthenticationBuilderFactory(dataAccessConfigBuilderFactory); } @Bean public CustomMessageConverter authenticationCustomMessageConverter(AuthenticationBuilderFactory factory) { return new CustomMessageConverter() { @Override public boolean support(Class clazz) { return clazz == Authentication.class; } @Override public Object convert(Class clazz, byte[] message) { String json = new String(message); return factory.create().json(json).build(); } }; } @Bean @ConditionalOnMissingBean(DimensionManager.class) public DimensionManager defaultDimensionManager(ObjectProviderbindProviders, ObjectProvider providers){ DefaultDimensionManager manager = new DefaultDimensionManager(); bindProviders.forEach(manager::addBindProvider); providers.forEach(manager::addProvider); return manager; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultDimensionManager.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionProvider; import org.hswebframework.web.authorization.dimension.DimensionManager; import org.hswebframework.web.authorization.dimension.DimensionUserBind; import org.hswebframework.web.authorization.dimension.DimensionUserBindProvider; import org.hswebframework.web.authorization.dimension.DimensionUserDetail; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; import java.util.stream.Collectors; public class DefaultDimensionManager implements DimensionManager { private final List dimensionProviders = new CopyOnWriteArrayList<>(); private final List bindProviders = new CopyOnWriteArrayList<>(); private final Mono> providerMapping = Flux .defer(() -> Flux.fromIterable(dimensionProviders)) .flatMap(provider -> provider .getAllType() .map(type -> Tuples.of(type.getId(), provider))) .collectMap(Tuple2::getT1, Tuple2::getT2); public DefaultDimensionManager() { } public void addProvider(DimensionProvider provider) { dimensionProviders.add(provider); } public void addBindProvider(DimensionUserBindProvider bindProvider) { bindProviders.add(bindProvider); } private Mono> providerMapping() { return providerMapping; } @Override public Flux getUserDimension(Collection userId) { return this .providerMapping() .flatMapMany(providerMapping -> Flux .fromIterable(bindProviders) //获取绑定信息 .flatMap(provider -> provider.getDimensionBindInfo(userId)) .groupBy(DimensionUserBind::getDimensionType) .flatMap(group -> { String type = group.key(); Flux binds = group.cache(); DimensionProvider provider = providerMapping.get(type); if (null == provider) { return Mono.empty(); } //获取维度信息 return binds .map(DimensionUserBind::getDimensionId) .collect(Collectors.toSet()) .flatMapMany(idList -> provider.getDimensionsById(SimpleDimensionType.of(type), idList)) .collectMap(Dimension::getId, Function.identity()) .flatMapMany(mapping -> binds .groupBy(DimensionUserBind::getUserId) .flatMap(userGroup -> Mono .zip( Mono.just(userGroup.key()), userGroup .handle((bind, sink) -> { Dimension dimension = mapping.get(bind.getDimensionId()); if (dimension != null) { sink.next(dimension); } }) .collectList(), DimensionUserDetail::of )) ); }) ) .groupBy(DimensionUserDetail::getUserId) .flatMap(group->group.reduce(DimensionUserDetail::merge)); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DimensionDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DefaultDataAccessType; import org.hswebframework.web.authorization.access.ScopeDataAccessConfig; import org.hswebframework.web.authorization.simple.AbstractDataAccessConfig; import java.util.Set; @Getter @Setter @EqualsAndHashCode(callSuper = true) public class DimensionDataAccessConfig extends AbstractDataAccessConfig implements ScopeDataAccessConfig { private Set scope; private boolean children; /** * @see DimensionType#getId() */ private String scopeType; @Override public DefaultDataAccessType getType() { return DefaultDataAccessType.DIMENSION_SCOPE; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/PlainTextUsernamePasswordAuthenticationRequest.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.authorization.AuthenticationRequest; /** * @author zhouhao * @since 3.0.0-RC */ @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class PlainTextUsernamePasswordAuthenticationRequest implements AuthenticationRequest { private String username; private String password; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java ================================================ /* * 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.authorization.simple; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.*; import java.io.Serial; import java.io.Serializable; import java.util.*; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; public class SimpleAuthentication implements Authentication { static final AtomicLongFieldUpdater ACCESS_COUNT_UPDATER = AtomicLongFieldUpdater.newUpdater(SimpleAuthentication.class, "accessCount"); @Serial private static final long serialVersionUID = -2898863220255336528L; @Getter private User user; @Setter private List permissions = new ArrayList<>(); private List dimensions = new ArrayList<>(); @Setter private Map attributes = new HashMap<>(); public static Authentication of() { return new SimpleAuthentication(); } @Override @SuppressWarnings("unchecked") public Optional getAttribute(String name) { return Optional.ofNullable((T) attributes.get(name)); } public List getDimensions() { return dimensions == null ? Collections.emptyList() : dimensions; } public List getPermissions() { return permissions == null ? Collections.emptyList() : permissions; } @Override public Map getAttributes() { return attributes == null ? Collections.emptyMap() : attributes; } public SimpleAuthentication merge(Authentication authentication) { Map mePermissionGroup = permissions .stream() .collect(Collectors.toMap(Permission::getId, Function.identity())); if (authentication.getUser() != null) { user = authentication.getUser(); } this.attributes = new HashMap<>(getAttributes()); this.attributes.putAll(authentication.getAttributes()); this.permissions = new ArrayList<>(this.getPermissions()); for (Permission permission : authentication.getPermissions()) { Permission me = mePermissionGroup.get(permission.getId()); if (me == null) { permissions.add(permission.copy()); continue; } me.getActions().addAll(permission.getActions()); } this.dimensions = new ArrayList<>(this.getDimensions()); for (Dimension dimension : authentication.getDimensions()) { if (getDimension(dimension.getType(), dimension.getId()).isEmpty()) { dimensions.add(dimension); } } return this; } protected SimpleAuthentication newInstance() { return new SimpleAuthentication(); } @Override public Authentication copy(BiPredicate permissionFilter, Predicate dimension) { SimpleAuthentication authentication = newInstance(); 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()) ); if (user != null) { authentication.setUser0(user); } authentication.setAttributes(new HashMap<>(attributes)); return authentication; } public void setUser(User user) { this.user = user; dimensions.add(user); } protected void setUser0(User user) { this.user = user; } public void setDimensions(List dimensions) { this.dimensions.addAll(dimensions); } public void setDimensions(Collection dimensions) { this.dimensions.addAll(dimensions); } public void addDimension(Dimension dimension) { this.dimensions.add(dimension); } private transient volatile Map> dimensionMapping; private transient volatile Map permissionMapping; private transient volatile long accessCount; protected boolean fastPath() { // 总共访问超过8次,则进行初始化缓存. if (ACCESS_COUNT_UPDATER.incrementAndGet(this) == 8) { if (permissionMapping == null) { permissionMapping = permissions == null ? Collections.emptyMap() : permissions .stream() .collect(Collectors .toMap(Permission::getId, Function.identity(), (a, b) -> b)); dimensionMapping = dimensions == null ? Collections.emptyMap() : dimensions .stream() .collect(Collectors .groupingBy(d -> d.getType().getId(), Collectors.toMap( Dimension::getId, Function.identity(), (a, b) -> a))); } } return permissionMapping != null; } @Override public boolean hasPermission(String permissionId, Collection actions) { Map permissionMapping = this.permissionMapping; if (fastPath() && permissionMapping != null) { Permission permission = permissionMapping.get(permissionId); if (permission == null) { permission = permissionMapping.get("*"); } if (permission == null) { return false; } return actions.isEmpty() || permission.getActions().containsAll(actions) || permission.getActions().contains("*"); } return Authentication.super.hasPermission(permissionId, actions); } @Override public Optional getDimension(String type, String id) { Map> dimensionMapping = this.dimensionMapping; if (fastPath() && dimensionMapping != null) { Map mapping = dimensionMapping.get(type); if (mapping == null) { return Optional.empty(); } return Optional.ofNullable(mapping.get(id)); } return Authentication.super.getDimension(type, id); } @Override public Optional getDimension(DimensionType type, String id) { return getDimension(type.getId(), id); } @Override public List getDimensions(DimensionType type) { return this.getDimensions(type.getId()); } @Override public List getDimensions(String type) { Map> dimensionMapping = this.dimensionMapping; if (fastPath() && dimensionMapping != null) { Map mapping = dimensionMapping.get(type); if (mapping == null) { return List.of(); } return new ArrayList<>(mapping.values()); } return Authentication.super.getDimensions(type); } @Override public Optional getPermission(String id) { Map permissionMapping = this.permissionMapping; if (fastPath() && permissionMapping != null) { return Optional.ofNullable(permissionMapping.get(id)); } return Authentication.super.getPermission(id); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleDimension.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionType; import java.util.Map; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor @EqualsAndHashCode public class SimpleDimension implements Dimension { private String id; private String name; private DimensionType type; private Map options; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleDimensionType.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.DimensionType; import java.io.Serializable; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor @EqualsAndHashCode public class SimpleDimensionType implements DimensionType, Serializable { private static final long serialVersionUID = -6849794470754667710L; private String id; private String name; public static SimpleDimensionType of(String id) { return of(id, id); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleFieldFilterDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DefaultDataAccessType; import org.hswebframework.web.authorization.access.FieldFilterDataAccessConfig; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import static org.hswebframework.web.authorization.access.DataAccessConfig.DefaultType.DENY_FIELDS; /** * 默认配置实现 * * @author zhouhao * @see FieldFilterDataAccessConfig * @since 3.0 */ public class SimpleFieldFilterDataAccessConfig extends AbstractDataAccessConfig implements FieldFilterDataAccessConfig { private static final long serialVersionUID = 8080660575093151866L; private Set fields; public SimpleFieldFilterDataAccessConfig() { } public SimpleFieldFilterDataAccessConfig(String... fields) { this.fields = new HashSet<>(Arrays.asList(fields)); } @Override public Set getFields() { return fields; } public void setFields(Set fields) { this.fields = fields; } @Override public DataAccessType getType() { return DefaultDataAccessType.FIELD_DENY; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleOwnCreatedDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.OwnCreatedDataAccessConfig; /** * @author zhouhao * @since 3.0 */ public class SimpleOwnCreatedDataAccessConfig extends AbstractDataAccessConfig implements OwnCreatedDataAccessConfig { private static final long serialVersionUID = -6059330812806119730L; public SimpleOwnCreatedDataAccessConfig() { } public SimpleOwnCreatedDataAccessConfig(String action) { setAction(action); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.apache.commons.collections4.CollectionUtils; 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 */ @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(exclude = "dataAccesses") public class SimplePermission implements Permission { private static final long serialVersionUID = 7587266693680162184L; private String id; private String name; private Set actions; private Set dataAccesses; private Map options; public Set getActions() { if (actions == null) { actions = new java.util.HashSet<>(); } return actions; } public Set getDataAccesses() { if (dataAccesses == null) { dataAccesses = new java.util.HashSet<>(); } return dataAccesses; } @Override public Permission copy(Predicate actionFilter, Predicate dataAccessFilter) { SimplePermission permission = new SimplePermission(); permission.setId(id); permission.setName(name); 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); } @Override public String toString() { return id + (CollectionUtils.isNotEmpty(actions) ? ":" + String.join(",", actions) : ""); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleRole.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.Role; import java.io.Serializable; import java.util.Map; /** * @author zhouhao */ @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode public class SimpleRole implements Role { private static final long serialVersionUID = 7460859165231311347L; private String id; private String name; private Map options; public static Role of(Dimension dimension) { return SimpleRole.builder() .name(dimension.getName()) .id(dimension.getId()) .options(dimension.getOptions()) .build(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleUser.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.User; import java.io.Serial; import java.io.Serializable; import java.util.Map; /** * @author zhouhao */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @EqualsAndHashCode public class SimpleUser implements User { @Serial private static final long serialVersionUID = 2194541828191869091L; private String id; private String username; private String name; private String userType; private Map options; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/builder/DataAccessConfigConverter.java ================================================ package org.hswebframework.web.authorization.simple.builder; import org.hswebframework.web.authorization.access.DataAccessConfig; /** * @author zhouhao */ public interface DataAccessConfigConverter { boolean isSupport(String type, String action, String config); DataAccessConfig convert(String type, String action, String config); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/builder/SimpleAuthenticationBuilder.java ================================================ package org.hswebframework.web.authorization.simple.builder; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Maps; import org.hswebframework.web.authorization.*; import org.hswebframework.web.authorization.builder.AuthenticationBuilder; import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory; import org.hswebframework.web.authorization.simple.*; import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; /** * @author zhouhao */ public class SimpleAuthenticationBuilder implements AuthenticationBuilder { private SimpleAuthentication authentication = new SimpleAuthentication(); private DataAccessConfigBuilderFactory dataBuilderFactory; public SimpleAuthenticationBuilder(DataAccessConfigBuilderFactory dataBuilderFactory) { this.dataBuilderFactory = dataBuilderFactory; } public void setDataBuilderFactory(DataAccessConfigBuilderFactory dataBuilderFactory) { this.dataBuilderFactory = dataBuilderFactory; } @Override public AuthenticationBuilder user(User user) { Objects.requireNonNull(user); authentication.setUser(user); return this; } @Override public AuthenticationBuilder user(String user) { return user(JSON.parseObject(user, SimpleUser.class)); } @Override public AuthenticationBuilder user(Map user) { Objects.requireNonNull(user.get("id")); user(SimpleUser.builder() .id(user.get("id")) .username(user.get("username")) .name(user.get("name")) .userType(user.get("type")) .build()); return this; } @Override public AuthenticationBuilder role(List role) { authentication.getDimensions().addAll(role); return this; } @Override @SuppressWarnings("unchecked") public AuthenticationBuilder role(String role) { return role((List) JSON.parseArray(role, SimpleRole.class)); } @Override public AuthenticationBuilder permission(List permission) { authentication.setPermissions(permission); return this; } public AuthenticationBuilder permission(JSONArray jsonArray) { List permissions = new ArrayList<>(); for (int i = 0; i < jsonArray.size(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); SimplePermission permission = new SimplePermission(); permission.setId(jsonObject.getString("id")); permission.setName(jsonObject.getString("name")); permission.setOptions(jsonObject.getJSONObject("options")); JSONArray actions = jsonObject.getJSONArray("actions"); if (actions != null) { permission.setActions(new HashSet<>(actions.toJavaList(String.class))); } JSONArray dataAccess = jsonObject.getJSONArray("dataAccesses"); if (null != dataAccess) { permission.setDataAccesses(dataAccess.stream().map(JSONObject.class::cast) .map(dataJson -> dataBuilderFactory .create() .fromJson(dataJson.toJSONString()) .build()) .filter(Objects::nonNull) .collect(Collectors.toSet())); } permissions.add(permission); } authentication.setPermissions(permissions); return this; } @Override public AuthenticationBuilder permission(String permissionJson) { return permission(JSON.parseArray(permissionJson)); } @Override public AuthenticationBuilder attributes(String attributes) { authentication.getAttributes().putAll(JSON.>parseObject(attributes, Map.class)); return this; } @Override public AuthenticationBuilder attributes(Map permission) { authentication.getAttributes().putAll(permission); return this; } public AuthenticationBuilder dimension(JSONArray json) { if (json == null) { return this; } List dimensions = new ArrayList<>(); for (int i = 0; i < json.size(); i++) { JSONObject jsonObject = json.getJSONObject(i); Object type = jsonObject.get("type"); Map
* public Mono<User> getUser(){ * return Authentication.currentReactive() * .switchIfEmpty(Mono.error(new UnAuthorizedException())) * .flatMap(autz->findUserByUserId(autz.getUser().getId())); * } *
* * Authentication auth= Authentication.current().get(); * //如果权限信息不存在将抛出{@link NoSuchElementException}建议使用下面的方式获取 * Authentication auth=Authentication.current().orElse(null); * //或者 * Authentication auth=Authentication.current().orElseThrow(UnAuthorizedException::new); *
* @RequestMapping("/example") * public ResponseMessage example(){ * Authorization auth = AuthorizationHolder.get(); * return ResponseMessage.ok(); * } *
* ⚠️:任何时候都不应该对返回的Set进行写操作 * * @return 如果没有配置返回空{@link Collections#emptySet()},不会返回null. */ Set getActions(); /** * 用户对此权限持有的数据权限信息, 用于数据级别的控制 * * ⚠️:任何时候都不应该对返回的Set进行写操作 * * @return 如果没有配置返回空{@link Collections#emptySet()},不会返回null. * @see DataAccessConfig * @see org.hswebframework.web.authorization.access.DataAccessController */ @Deprecated Set getDataAccesses(); default Set getDataAccesses(String action) { return getDataAccesses() .stream() .filter(conf -> conf.getAction().equals(action)) .collect(Collectors.toSet()); } /** * 查找数据权限配置 * * @param configPredicate 数据权限配置匹配规则 * @param 数据权限配置类型 * @return {@link Optional} * @see this#scope(String, String, String) */ @SuppressWarnings("all") default Optional findDataAccess(DataAccessPredicate configPredicate) { return (Optional) getDataAccesses().stream() .filter(configPredicate) .findFirst(); } /** * 查找字段过滤的数据权限配置(列级数据权限),比如:不查询某些字段 * * @param action 权限操作类型 {@link Permission#ACTION_QUERY} * @return {@link Optional} * @see FieldFilterDataAccessConfig * @see FieldFilterDataAccessConfig#getFields() */ default Optional findFieldFilter(String action) { return findDataAccess(conf -> conf instanceof FieldFilterDataAccessConfig && conf.getAction().equals(action)); } /** * 获取不能执行操作的字段 * * @param action 权限操作 * @return 未配置时返回空set, 不会返回null */ default Set findDenyFields(String action) { return findFieldFilter(action) .filter(conf -> DENY_FIELDS.equals(conf.getType().getId())) .map(FieldFilterDataAccessConfig::getFields) .orElseGet(Collections::emptySet); } /** * 查找数据范围权限控制配置(行级数据权限),比如: 只能查询本机构的数据 * * @param type 范围类型标识,由具体的实现定义,如: 机构范围 * @param scopeType 范围类型,由具体的实现定义,如: 只能查看自己所在机构 * @param action 权限操作 {@link Permission#ACTION_QUERY} * @return 未配置时返回空set, 不会返回null */ default Set findScope(String action, String type, String scopeType) { return findScope(scope(action, type, scopeType)); } default Set findScope(Permission.DataAccessPredicate predicate) { return findDataAccess(predicate) .map(ScopeDataAccessConfig::getScope) .orElseGet(Collections::emptySet); } /** * 构造一个数据范围权限控制配置查找逻辑 * * @param type 范围类型标识,由具体的实现定义,如: 机构范围 * @param scopeType 范围类型,由具体的实现定义,如: 只能查看自己所在机构 * @param action 权限操作 {@link Permission#ACTION_QUERY} * @return {@link DataAccessPredicate} */ static Permission.DataAccessPredicate scope(String action, String type, String scopeType) { Objects.requireNonNull(action, "action can not be null"); Objects.requireNonNull(type, "type can not be null"); Objects.requireNonNull(scopeType, "scopeType can not be null"); return config -> config instanceof ScopeDataAccessConfig && action.equals(config.getAction()) && type.equals(config.getType()) && scopeType.equals(((ScopeDataAccessConfig) config).getScopeType()); } Permission copy(); Permission copy(Predicate actionFilter,Predicate dataAccessFilter); /** * 数据权限查找判断逻辑接口 * * @param */ interface DataAccessPredicate extends Predicate { boolean test(DataAccessConfig config); @Override default DataAccessPredicate and(Predicate super DataAccessConfig> other) { return (t) -> test(t) && other.test(t); } @Override default DataAccessPredicate or(Predicate super DataAccessConfig> other) { return (t) -> test(t) || other.test(t); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationHolder.java ================================================ /* * Copyright 2019 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.authorization; import com.google.common.collect.Lists; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.context.Context; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; /** * 响应式权限保持器,用于响应式方式获取当前登录用户的权限信息. * 例如: * {@code * @RequestMapping("/example") * public Mono example(){ * return ReactiveAuthenticationHolder.get(); * } * } * * * @author zhouhao * @see ReactiveAuthenticationSupplier * @since 4.0 */ public final class ReactiveAuthenticationHolder { private static final List suppliers = new CopyOnWriteArrayList<>(); public static final String IGNORE_AUTH_KEY = ".auth.ignore"; static final Context IGNORE_AUTH_CONTEXT_Y = Context.of(IGNORE_AUTH_KEY, true); static final Context IGNORE_AUTH_CONTEXT_N = Context.of(IGNORE_AUTH_KEY, false); private static Mono get(Function> function) { return AuthenticationUtils .merge(Flux.merge(Lists.transform(suppliers, function::apply))); } /** * @return 当前登录的用户权限信息 */ public static Mono get() { return Mono.deferContextual(ctx -> { if (Boolean.TRUE.equals(ctx.getOrDefault(IGNORE_AUTH_KEY, false))) { return Mono.empty(); } return get(ReactiveAuthenticationSupplier::get); }); } /** * 获取指定用户的权限信息 * * @param userId 用户ID * @return 权限信息 */ public static Mono get(String userId) { return get(supplier -> supplier.get(userId)); } /** * 初始化 {@link ReactiveAuthenticationSupplier} * * @param supplier */ public static void addSupplier(ReactiveAuthenticationSupplier supplier) { suppliers.add(supplier); } public static void setSupplier(ReactiveAuthenticationSupplier supplier) { suppliers.clear(); suppliers.add(supplier); } public static Context ignoreContext(boolean ignore) { return ignore ? IGNORE_AUTH_CONTEXT_Y : IGNORE_AUTH_CONTEXT_N; } public static Function ignoreIfAbsent(boolean ignore) { return ctx -> ctx.hasKey(IGNORE_AUTH_KEY) ? ctx : ctx.put(IGNORE_AUTH_KEY, ignore); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationInitializeService.java ================================================ /* * 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.authorization; import org.hswebframework.web.authorization.events.AuthorizationInitializeEvent; import reactor.core.publisher.Mono; /** * 授权信息初始化服务接口,使用该接口初始化用的权限信息 * * @author zhouhao * @since 4.0 */ public interface ReactiveAuthenticationInitializeService { /** * 根据用户ID初始化权限信息 * * @param userId 用户ID * @return 权限信息 * @see AuthorizationInitializeEvent */ Mono initUserAuthorization(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationManager.java ================================================ /* * 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.authorization; import reactor.core.publisher.Mono; /** * 授权信息管理器,用于获取用户授权和同步授权信息 * * @author zhouhao * @see 3.0 */ public interface ReactiveAuthenticationManager { /** * 进行授权操作 * * @param request 授权请求 * @return 授权成功则返回用户权限信息 */ Mono authenticate(Mono request); /** * 根据用户ID获取权限信息 * * @param userId 用户ID * @return 权限信息 */ Mono getByUserId(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationManagerProvider.java ================================================ package org.hswebframework.web.authorization; import reactor.core.publisher.Mono; public interface ReactiveAuthenticationManagerProvider { /** * 进行授权操作 * * @param request 授权请求 * @return 授权成功则返回用户权限信息 */ Mono authenticate(Mono request); /** * 根据用户ID获取权限信息 * * @param userId 用户ID * @return 权限信息 */ Mono getByUserId(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationSupplier.java ================================================ /* * 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.authorization; import reactor.core.publisher.Mono; import java.util.function.Supplier; /** * @author zhouhao * @see Supplier * @see Authentication * @see ReactiveAuthenticationHolder * @since 4.0 */ public interface ReactiveAuthenticationSupplier extends Supplier> { Mono get(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Role.java ================================================ /* * 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.authorization; import org.hswebframework.web.authorization.simple.SimpleRole; /** * 角色信息 * * @author zhouhao * @since 3.0 */ public interface Role extends Dimension { /** * @return 角色ID */ String getId(); /** * @return 角色名 */ String getName(); @Override default DimensionType getType() { return DefaultDimensionType.role; } static Role fromDimension(Dimension dimension){ return SimpleRole.of(dimension); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/User.java ================================================ /* * 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.authorization; /** * 用户信息 * * @author zhouhao * @since 3.0 */ public interface User extends Dimension { /** * @return 用户ID */ String getId(); /** * @return 用户名 */ String getUsername(); /** * @return 姓名 */ String getName(); /** * @return 用户类型 */ String getUserType(); @Override default DimensionType getType() { return DefaultDimensionType.user; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessConfig.java ================================================ /* * 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.authorization.access; import org.hswebframework.web.authorization.Permission; import java.io.Serializable; /** * 数据级的权限控制,此接口为控制方式配置 * 具体的控制逻辑由控制器{@link DataAccessController}实现 * * @author zhouhao * @see OwnCreatedDataAccessConfig */ public interface DataAccessConfig extends Serializable { /** * 对数据的操作事件 * * @return 操作时间 * @see Permission#ACTION_ADD * @see Permission#ACTION_DELETE * @see Permission#ACTION_GET * @see Permission#ACTION_QUERY * @see Permission#ACTION_UPDATE */ String getAction(); /** * 控制方式标识 * * @return 控制方式 * @see DefaultType */ DataAccessType getType(); /** * 内置的控制方式 */ interface DefaultType { /** * 自己创建的数据 * * @see OwnCreatedDataAccessConfig#getType() */ String OWN_CREATED = "OWN_CREATED"; /** * 禁止操作字段 * * @see FieldFilterDataAccessConfig#getType() */ String DENY_FIELDS = "DENY_FIELDS"; /** * 禁止操作字段 * * @see org.hswebframework.web.authorization.simple.DimensionDataAccessConfig#getType() */ String DIMENSION_SCOPE = "DIMENSION_SCOPE"; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessConfiguration.java ================================================ package org.hswebframework.web.authorization.access; public interface DataAccessConfiguration { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessController.java ================================================ package org.hswebframework.web.authorization.access; import org.hswebframework.web.authorization.define.AuthorizingContext; /** * 数据级别权限控制器,通过此控制器对当前登录用户进行的操作进行数据级别的权限控制。 * 如:A用户只能查询自己创建的B数据,A用户只能修改自己创建的B数据 * * @author zhouhao * @since 3.0 */ @Deprecated public interface DataAccessController { /** * 执行权限控制 * @param access 控制方式以及配置 * @param context 权限验证上下文,用于传递验证过程用到的参数 * @return 授权是否通过 */ boolean doAccess(DataAccessConfig access, AuthorizingContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessHandler.java ================================================ package org.hswebframework.web.authorization.access; import org.hswebframework.web.authorization.define.AuthorizingContext; /** * 数据级别权限控制处理器接口,负责处理支持的权限控制配置 * * @author zhouhao */ public interface DataAccessHandler { /** * 是否支持处理此配置 * * @param access 控制配置 * @return 是否支持 */ boolean isSupport(DataAccessConfig access); /** * 执行处理,返回处理结果 * * @param access 控制配置 * @param context 参数上下文 * @return 处理结果 */ boolean handle(DataAccessConfig access, AuthorizingContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessType.java ================================================ package org.hswebframework.web.authorization.access; public interface DataAccessType { String getId(); String getName(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DefaultDataAccessType.java ================================================ package org.hswebframework.web.authorization.access; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.web.dict.Dict; import org.hswebframework.web.dict.EnumDict; @Getter @AllArgsConstructor public enum DefaultDataAccessType implements DataAccessType, EnumDict { USER_OWN_DATA("自己的数据"), FIELD_DENY("禁止操作字段"), DIMENSION_SCOPE("维度范围"); private final String name; @Override public String getText() { return name; } @Override public String getValue() { return getId(); } @Override public String getId() { return name().toLowerCase(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DimensionHelper.java ================================================ package org.hswebframework.web.authorization.access; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.simple.DimensionDataAccessConfig; import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; @NoArgsConstructor(access = AccessLevel.PRIVATE) public abstract class DimensionHelper { public static Set getDimensionDataAccessScope(Authentication atz, Permission permission, String action, String dimensionType) { return permission .getDataAccesses(action) .stream() .filter(DimensionDataAccessConfig.class::isInstance) .map(DimensionDataAccessConfig.class::cast) .filter(conf -> dimensionType.equals(conf.getScopeType())) .flatMap(conf -> { if (CollectionUtils.isEmpty(conf.getScope())) { return atz.getDimensions(dimensionType) .stream() .map(Dimension::getId); } return conf.getScope().stream(); }).collect(Collectors.toSet()); } public static Set getDimensionDataAccessScope(Authentication atz, Permission permission, String action, DimensionType dimensionType) { return getDimensionDataAccessScope(atz, permission, action, dimensionType.getId()); } public static Set getDimensionDataAccessScope(Authentication atz, String permission, String action, String dimensionType) { return atz .getPermission(permission) .map(per -> getDimensionDataAccessScope(atz, per, action, dimensionType)).orElseGet(Collections::emptySet); } public static Set getDimensionDataAccessScope(Authentication atz, String permission, String action, DimensionType dimensionType) { return atz .getPermission(permission) .map(per -> getDimensionDataAccessScope(atz, per, action, dimensionType)) .orElseGet(Collections::emptySet); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/FieldFilterDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.access; import java.util.Set; /** * 对字段进行过滤操作配置,实现字段级别的权限控制 * * @author zhouhao * @see DataAccessConfig * @see org.hswebframework.web.authorization.simple.SimpleFieldFilterDataAccessConfig */ public interface FieldFilterDataAccessConfig extends DataAccessConfig { Set getFields(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/OwnCreatedDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.access; /** * 只能操作由自己创建的数据 * * @author zhouhao */ public interface OwnCreatedDataAccessConfig extends DataAccessConfig { @Override default DataAccessType getType() { return DefaultDataAccessType.USER_OWN_DATA; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/ScopeDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.access; import java.util.Set; /** * 范围数据权限控制配置 * * @author zhouhao * @see DataAccessConfig * @since 3.0 */ public interface ScopeDataAccessConfig extends DataAccessConfig { /** * @return 范围类型 */ String getScopeType(); /** * @return 自定义的控制范围 */ Set getScope(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/UserAttachEntity.java ================================================ package org.hswebframework.web.authorization.access; /** * 和user关联的实体 * * @author zhouhao * @since 3.0.6 */ public interface UserAttachEntity { String userId = "userId"; String getUserId(); void setUserId(String userId); default String getUserIdProperty() { return userId; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java ================================================ /* * * * 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.authorization.annotation; import org.hswebframework.web.authorization.define.Phased; import java.lang.annotation.*; /** * 基础权限控制注解,提供基本的控制配置 * * @author zhouhao * @see org.hswebframework.web.authorization.Authentication * @see org.hswebframework.web.authorization.define.AuthorizeDefinition * @see Resource * @see ResourceAction * @see Dimension * @see DataAccess * @since 3.0 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Authorize { Resource[] resources() default {}; Dimension[] dimension() default {}; /** * 是否运行匿名访问,匿名访问时,直接允许执行,否则将进行权限验证. * * @return 是否允许匿名访问 * @since 4.0.19 */ boolean anonymous() default false; /** * 验证失败时返回的消息 * * @return 验证失败提示的消息 */ String message() default "无访问权限"; /** * 是否合并类上的注解 * * @return 是否合并类上的注解 */ boolean merge() default true; /** * 验证模式,在使用多个验证条件时有效 * * @return logical */ Logical logical() default Logical.DEFAULT; /** * @return 验证时机,在方法调用前还是调用后 */ Phased phased() default Phased.before; /** * @return 是否忽略, 忽略后将不进行权限控制 */ boolean ignore() default false; String[] description() default {}; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/CreateAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_ADD, name = "新增") public @interface CreateAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccess.java ================================================ /* * 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.authorization.annotation; import org.hswebframework.web.authorization.access.DataAccessController; import java.lang.annotation.*; /** * 数据级权限控制注解,用于进行需要数据级别权限控制的声明. * * 此注解仅用于声明此方法需要进行数据级权限控制,具体权限控制方式由控制器实{@link DataAccessController}现 * * * @author zhouhao * @see DataAccessController * @see ResourceAction#dataAccess() * @since 3.0 * @deprecated 已弃用, 4.1中移除 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Deprecated public @interface DataAccess { DataAccessType[] type() default {}; /** * @return logical */ Logical logical() default Logical.AND; /** * @return 是否忽略, 忽略后将不进行权限控制 */ boolean ignore() default false; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccessType.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.access.DataAccessConfiguration; import org.hswebframework.web.authorization.access.DataAccessController; import java.lang.annotation.*; @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Deprecated public @interface DataAccessType { String id(); //标识 String name(); //名称 String[] description() default {}; /** * @see DataAccessController */ Class extends DataAccessController> controller() default DataAccessController.class; Class extends DataAccessConfiguration> configuration() default DataAccessConfiguration.class; boolean ignore() default false; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DeleteAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_DELETE, name = "删除") public @interface DeleteAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Dimension.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.DimensionType; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 请使用注解继承方式使用此注解 * * @author zhouhao * @see RequiresRoles * @since 4.0 */ @Target({ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(value = Dimension.List.class) public @interface Dimension { /** * 维度类型标识,如: role,org * * @return 维度类型 * @see org.hswebframework.web.authorization.Dimension#getType() * @see DimensionType#getId() * @see org.hswebframework.web.authorization.Authentication#hasDimension(String, String...) */ String type(); /** * 具体的维度ID,如: 角色ID,组织ID * * @return 维度ID * @see org.hswebframework.web.authorization.Dimension#getId() * @see org.hswebframework.web.authorization.Authentication#hasDimension(String, String...) */ String[] id() default {}; /** * 配置了多个ID时的判断逻辑,默认为任意满足则认为有权限. * * @return Logical */ Logical logical() default Logical.DEFAULT; /** * @return 说明 */ String[] description() default {}; /** * @return 是否忽略 */ boolean ignore() default false; @Target({ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @Inherited @interface List { Dimension[] value() default {}; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DimensionDataAccess.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.define.Phased; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @DataAccessType(id = "dimension", name = "维度数据权限") @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Authorize @Deprecated public @interface DimensionDataAccess { Mapping[] mapping() default {}; @AliasFor(annotation = Authorize.class) Phased phased() default Phased.before; @AliasFor(annotation = DataAccessType.class) boolean ignore() default false; @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @interface Mapping { String dimensionType(); String property(); int idParamIndex() default -1; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Dimensions.java ================================================ package org.hswebframework.web.authorization.annotation; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 标记多个维度的权限控制相关配置 * * @author zhouhao * @since 5.0.1 */ @Target({ElementType.METHOD, TYPE, ANNOTATION_TYPE, FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Dimensions { /** * 存在多个维度时的判断逻辑,默认任意一个满足则认为有权限 * * @return Logical */ Logical logical() default Logical.DEFAULT; /** * @return 针对当前配置的说明信息 */ String[] description() default {}; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/FieldDataAccess.java ================================================ package org.hswebframework.web.authorization.annotation; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; /** * @deprecated 已弃用 */ @DataAccessType(id = "FIELD_DENY", name = "字段权限") @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Deprecated public @interface FieldDataAccess { @AliasFor(annotation = DataAccessType.class) boolean ignore() default false; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Logical.java ================================================ /* * 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.authorization.annotation; public enum Logical { AND, OR, DEFAULT } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/QueryAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_QUERY, name = "查询") public @interface QueryAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/RequiresRoles.java ================================================ package org.hswebframework.web.authorization.annotation; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 注解根据角色维度进行权限控制,具有权限的用户才可访问对应的方法. * * {@code * @RequiresRoles("admin") * public Mono handleRequest(){ * * } * } * * @author zhouhao * @see Dimension * @since 4.0 */ @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Dimension(type = "role") @Repeatable(RequiresRoles.List.class) public @interface RequiresRoles { /** * @return 角色ID */ @AliasFor(annotation = Dimension.class, attribute = "id") String[] value() default {}; /** * 多个角色时的判断逻辑 * @return Logical */ @AliasFor(annotation = Dimension.class, attribute = "logical") Logical logical() default Logical.DEFAULT; @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) @Retention(RUNTIME) @Documented @Inherited @Dimension.List() @interface List { RequiresRoles[] value(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Resource.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.define.Phased; import java.lang.annotation.*; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 接口资源声明注解,声明Controller的资源相关信息,用于进行权限控制。 * * 在Controller进行注解,表示此接口需要有对应的权限{@link Permission#getId()}才能进行访问. * 具体的操作权限控制,需要在方法上注解{@link ResourceAction}. * * * * {@code * @RestController * //声明资源 * @Resource(id = "test", name = "测试功能") * public class TestController implements ReactiveCrudController { * * //声明操作,需要有 test:query 权限才能访问此接口 * @QueryAction * public Mono getUser() { * return Authentication.currentReactive() * .switchIfEmpty(Mono.error(new UnAuthorizedException())) * .map(Authentication::getUser); * } * * } * } * * 如果接口不需要进行权限控制,可注解{@link Authorize#ignore()}来标识此接口不需要权限控制. * 或者通过监听 {@link org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent}来进行自定义处理 * {@code * @EventListener * public void handleAuthEvent(AuthorizingHandleBeforeEvent e) { * //admin用户可以访问全部操作 * if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) { * e.setAllow(true); * } * } * } * * @author zhouhao * @see ResourceAction * @see Authorize * @see org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent * @since 4.0 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(Resource.List.class) public @interface Resource { /** * 资源ID * * @return 资源ID */ String id(); /** * @return 资源名称 */ String name(); /** * @return 资源操作定义 */ ResourceAction[] actions() default {}; /** * @return 多个操作控制逻辑 */ Logical logical() default Logical.DEFAULT; /** * @return 权限控制阶段 */ Phased phased() default Phased.before; /** * @return 资源描述 */ String[] description() default {}; /** * @return 资源分组 */ String[] group() default {}; /** * 如果在方法上设置此属性,表示是否合并类上注解的属性 * * @return 是否合并 */ boolean merge() default true; @Target({ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @Inherited @interface List { Resource[] value(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/ResourceAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 对资源操作的描述,通常用来进行权限控制. * * 在Controller方法上添加此注解,来声明根据权限操作{@link Permission#getActions()}进行权限控制. * * 可以使用注解继承的方式来统一定义操作: * {@code * @Target(ElementType.METHOD) * @Retention(RetentionPolicy.RUNTIME) * @Inherited * @Documented * @ResourceAction(id = "create", name = "新增") * public @interface CreateAction { * * } * } * * * @see CreateAction * @see DeleteAction * @see SaveAction * @see org.hswebframework.web.authorization.Authentication * @see Permission#getActions() */ @Target({ANNOTATION_TYPE, FIELD, METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(ResourceAction.List.class) public @interface ResourceAction { /** * 操作标识 * * @return 操作标识 * @see Permission#getActions() */ String id(); /** * @return 操作名称 */ String name(); /** * @return 操作说明 */ String[] description() default {}; /** * @return 多个操作时的判断逻辑 */ Logical logical() default Logical.DEFAULT; @Target({ANNOTATION_TYPE, FIELD, METHOD}) @Retention(RUNTIME) @Documented @Inherited @interface List { ResourceAction[] value(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/SaveAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import java.lang.annotation.*; /** * 继承{@link ResourceAction},提供统一的id定义 * * @author zhouhao * @since 4.0 */ @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_SAVE, name = "保存") public @interface SaveAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/TwoFactor.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.twofactor.TwoFactorValidator; import java.lang.annotation.*; /** * 开启2FA双重验证 * * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidator * @since 3.0.4 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface TwoFactor { /** * @return 接口的标识, 用于实现不同的操作, 可能会配置不同的验证规则 */ String value(); /** * @return 验证有效期, 超过有效期后需要重新进行验证 */ long timeout() default 10 * 60 * 1000L; /** * 验证器供应商,如: totp,sms,email,由{@link org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider}进行自定义. * * 可通过配置项: hsweb.authorize.two-factor.default-provider 来修改默认配置 * * @return provider * @see TwoFactorValidator#getProvider() */ String provider() default "default"; /** * 验证码的http参数名,在进行验证的时候需要传入此参数 * * @return 验证码的参数名 */ String parameter() default "verifyCode"; /** * @return 关闭验证 */ boolean ignore() default false; /** * * @return 错误提示 * @since 3.0.6 */ String message() default "validation.verify_code_error"; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/UserOwnData.java ================================================ package org.hswebframework.web.authorization.annotation; import java.lang.annotation.*; /** * 声明某个操作支持用户查看自己的数据 * * @deprecated 已弃用 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @DataAccessType(id = "user_own_data", name = "用户自己的数据") @Deprecated public @interface UserOwnData { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/AuthenticationBuilder.java ================================================ /* * 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.authorization.builder; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.Role; import org.hswebframework.web.authorization.User; import java.io.Serializable; import java.util.List; import java.util.Map; public interface AuthenticationBuilder extends Serializable { AuthenticationBuilder user(User user); AuthenticationBuilder user(String user); AuthenticationBuilder user(Map user); AuthenticationBuilder role(List role); AuthenticationBuilder role(String role); AuthenticationBuilder permission(List permission); AuthenticationBuilder permission(String permission); AuthenticationBuilder attributes(String attributes); AuthenticationBuilder attributes(Map permission); AuthenticationBuilder json(String json); Authentication build(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/AuthenticationBuilderFactory.java ================================================ package org.hswebframework.web.authorization.builder; /** * 权限构造器工厂 * * @author zhouhao */ public interface AuthenticationBuilderFactory { /** * @return 新建一个权限构造器 */ AuthenticationBuilder create(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/DataAccessConfigBuilder.java ================================================ package org.hswebframework.web.authorization.builder; import org.hswebframework.web.authorization.access.DataAccessConfig; import java.util.Map; /** * * @author zhouhao */ public interface DataAccessConfigBuilder { DataAccessConfigBuilder fromJson(String json); DataAccessConfigBuilder fromMap(Map json); DataAccessConfig build(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/DataAccessConfigBuilderFactory.java ================================================ package org.hswebframework.web.authorization.builder; /** * 数据权限配置构造器工厂 * * @author zhouhao */ public interface DataAccessConfigBuilderFactory { /** * @return 新建一个数据权限配置构造器工厂 */ DataAccessConfigBuilder create(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/context/AuthenticationThreadLocalAccessor.java ================================================ package org.hswebframework.web.authorization.context; import io.micrometer.context.ThreadLocalAccessor; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationHolder; import org.hswebframework.web.authorization.ReactiveAuthenticationHolder; import javax.annotation.Nonnull; public class AuthenticationThreadLocalAccessor implements ThreadLocalAccessor { static final Object KEY = Authentication.class; static { ReactiveAuthenticationHolder.addSupplier( new ThreadLocalReactiveAuthenticationSupplier() ); } @Override @Nonnull public Object key() { return KEY; } @Override public Authentication getValue() { return AuthenticationHolder.get().orElse(null); } @Override public void setValue() { AuthenticationHolder.resetCurrent(); } @Override public void setValue(@Nonnull Authentication value) { AuthenticationHolder.makeCurrent(value); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/context/ThreadLocalReactiveAuthenticationSupplier.java ================================================ package org.hswebframework.web.authorization.context; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationHolder; import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier; import reactor.core.publisher.Mono; class ThreadLocalReactiveAuthenticationSupplier implements ReactiveAuthenticationSupplier { @Override public Mono get(String userId) { return Mono.empty(); } @Override public Mono get() { return Mono.justOrEmpty(AuthenticationHolder.get()); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AopAuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.lang.reflect.Method; /** * @author zhouhao * @since 1.0 */ public interface AopAuthorizeDefinition extends AuthorizeDefinition { Class> getTargetClass(); Method getTargetMethod(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.util.StringJoiner; /** * 权限控制定义,定义权限控制的方式 * * @author zhouhao * @since 3.0 */ public interface AuthorizeDefinition { ResourcesDefinition getResources(); DimensionsDefinition getDimensions(); String getMessage(); Phased getPhased(); boolean isEmpty(); default boolean allowAnonymous() { return false; } default String getDescription() { ResourcesDefinition res = getResources(); StringJoiner joiner = new StringJoiner(";"); for (ResourceDefinition resource : res.getResources()) { joiner.add(resource.getId() + ":" + String.join(",", resource.getActionIds())); } return joiner.toString(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionContext.java ================================================ package org.hswebframework.web.authorization.define; public interface AuthorizeDefinitionContext { void addResource(ResourceDefinition def); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionCustomizer.java ================================================ package org.hswebframework.web.authorization.define; public interface AuthorizeDefinitionCustomizer { void custom(AuthorizeDefinitionContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionInitializedEvent.java ================================================ package org.hswebframework.web.authorization.define; import org.hswebframework.web.authorization.events.AuthorizationEvent; import org.springframework.context.ApplicationEvent; import java.util.List; public class AuthorizeDefinitionInitializedEvent extends ApplicationEvent implements AuthorizationEvent { private static final long serialVersionUID = -8185138454949381441L; public AuthorizeDefinitionInitializedEvent(List all) { super(all); } @SuppressWarnings("unchecked") public List getAllDefinition() { return ((List) getSource()); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizingContext.java ================================================ package org.hswebframework.web.authorization.define; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.aop.MethodInterceptorContext; import org.hswebframework.web.authorization.Authentication; /** * 权限控制上下文 */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class AuthorizingContext { private AuthorizeDefinition definition; private Authentication authentication; private MethodInterceptorContext paramContext; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/CompositeAuthorizeDefinitionCustomizer.java ================================================ package org.hswebframework.web.authorization.define; import lombok.AllArgsConstructor; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @AllArgsConstructor public class CompositeAuthorizeDefinitionCustomizer implements AuthorizeDefinitionCustomizer{ private final List customizers; public CompositeAuthorizeDefinitionCustomizer(Iterable customizers){ this(StreamSupport.stream(customizers.spliterator(),false).collect(Collectors.toList())); } @Override public void custom(AuthorizeDefinitionContext context) { for (AuthorizeDefinitionCustomizer customizer : customizers) { customizer.custom(context); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DataAccessDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.Getter; import lombok.Setter; import java.util.*; @Getter @Setter public class DataAccessDefinition { Set dataAccessTypes = new HashSet<>(); public Optional getType(String typeId) { return dataAccessTypes .stream() .filter(type -> type.getId() != null && type.getId().equalsIgnoreCase(typeId)) .findAny(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DataAccessTypeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.access.DataAccessController; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DataAccessConfiguration; import org.hswebframework.web.bean.FastBeanCopier; @Getter @Setter @EqualsAndHashCode(of = "id") public class DataAccessTypeDefinition implements DataAccessType { private String id; private String name; private String description; private Class extends DataAccessController> controller; private Class extends DataAccessConfiguration> configuration; public DataAccessTypeDefinition copy(){ return FastBeanCopier.copy(this,DataAccessTypeDefinition::new); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DimensionDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.annotation.Logical; import org.hswebframework.web.bean.FastBeanCopier; import reactor.function.Predicate3; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; @Getter @Setter @EqualsAndHashCode(of = "typeId") public class DimensionDefinition { private String typeId; private String typeName; private Set dimensionId = new HashSet<>(); private Logical logical = Logical.DEFAULT; public boolean hasDimension(Predicate3> filter) { return filter.test(typeId,logical, Collections.unmodifiableSet(dimensionId)); } public boolean hasDimension(Set dimensionIdPredicate) { if (logical == Logical.AND) { return dimensionIdPredicate.containsAll(dimensionId); } return dimensionId .stream() .anyMatch(dimensionIdPredicate::contains); } public boolean hasDimension(String id) { return dimensionId.contains(id); } public void addDimensionI(Set id) { dimensionId.addAll(id); } public DimensionDefinition copy() { return FastBeanCopier.copy(this, DimensionDefinition::new); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DimensionsDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Predicate; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.annotation.Logical; import reactor.function.Predicate3; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiPredicate; import java.util.stream.Collectors; @Getter @Setter public class DimensionsDefinition { private Map dimensionsMapping = new ConcurrentHashMap<>(); private Logical logical = Logical.DEFAULT; private String description; public Set getDimensions() { return new HashSet<>(dimensionsMapping.values()); } public void clear() { dimensionsMapping.clear(); } public void addDimension(DimensionDefinition definition) { DimensionDefinition old = dimensionsMapping.putIfAbsent(definition.getTypeId(), definition); if (old != null) { old.addDimensionI(definition.getDimensionId()); } } public boolean isEmpty() { return MapUtils.isEmpty(this.dimensionsMapping); } public boolean hasDimension(Dimension dimension) { DimensionDefinition def = dimensionsMapping.get(dimension.getType().getId()); return def != null && def.hasDimension(dimension.getId()); } public boolean hasDimension(Predicate3> filter) { if (logical == Logical.AND) { return dimensionsMapping .values() .stream() .allMatch(e -> e.hasDimension(filter)); } else { return dimensionsMapping .values() .stream() .anyMatch(e -> e.hasDimension(filter)); } } public boolean hasDimension(List dimensions) { if (logical == Logical.AND) { return dimensions.stream().allMatch(this::hasDimension); } return dimensions.stream().anyMatch(this::hasDimension); } @Override public String toString() { return dimensionsMapping .values() .stream() .map(d -> String.join(",", d.getDimensionId()) + "@" + d.getTypeId()) .collect(Collectors.joining(";")); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/HandleType.java ================================================ package org.hswebframework.web.authorization.define; public enum HandleType{ RBAC,DATA } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/MergedAuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.util.List; import java.util.Set; public class MergedAuthorizeDefinition implements AuthorizeDefinitionContext { private final ResourcesDefinition resources = new ResourcesDefinition(); private final DimensionsDefinition dimensions = new DimensionsDefinition(); public Set getResources() { return resources.getResources(); } public Set getDimensions() { return dimensions.getDimensions(); } public void addResource(ResourceDefinition resource) { resources.addResource(resource, true); } public void addDimension(DimensionDefinition resource) { dimensions.addDimension(resource); } public void merge(List definitions) { for (AuthorizeDefinition definition : definitions) { definition.getResources().getResources().forEach(this::addResource); definition.getDimensions().getDimensions().forEach(this::addDimension); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/Phased.java ================================================ package org.hswebframework.web.authorization.define; public enum Phased { before, after } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourceActionDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.bean.FastBeanCopier; import org.hswebframework.web.i18n.I18nSupportUtils; import org.hswebframework.web.i18n.MultipleI18nSupportEntity; import java.util.Collection; import java.util.HashMap; import java.util.Locale; import java.util.Map; import static org.hswebframework.web.authorization.define.ResourceDefinition.supportLocale; @Getter @Setter @EqualsAndHashCode(of = "id") public class ResourceActionDefinition implements MultipleI18nSupportEntity { private String id; private String name; private String description; private Map> i18nMessages; @Deprecated private DataAccessDefinition dataAccess = new DataAccessDefinition(); private final static String resolveActionPrefix = "hswebframework.web.system.action."; public ResourceActionDefinition copy() { return FastBeanCopier.copy(this, ResourceActionDefinition::new); } public Map> getI18nMessages() { if (org.springframework.util.CollectionUtils.isEmpty(i18nMessages)) { this.i18nMessages = I18nSupportUtils .putI18nMessages( resolveActionPrefix + this.id, "name", supportLocale, null, this.i18nMessages ); } return i18nMessages; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourceDefinition.java ================================================ package org.hswebframework.web.authorization.define; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.annotation.Logical; import org.hswebframework.web.bean.FastBeanCopier; import org.hswebframework.web.i18n.I18nSupportUtils; import org.hswebframework.web.i18n.MultipleI18nSupportEntity; import java.util.*; import java.util.stream.Collectors; @Getter @Setter @EqualsAndHashCode(of = "id") public class ResourceDefinition implements MultipleI18nSupportEntity { private String id; private String name; private String description; private Set actions = new HashSet<>(); private List group; private Map> i18nMessages; @Setter(value = AccessLevel.PRIVATE) @JsonIgnore private volatile Set actionIds; private Logical logical = Logical.DEFAULT; private Phased phased = Phased.before; public final static List supportLocale = new ArrayList<>(); static { supportLocale.add(Locale.CHINESE); supportLocale.add(Locale.ENGLISH); } private final static String resolvePermissionPrefix = "hswebframework.web.system.permission."; public static ResourceDefinition of(String id, String name) { ResourceDefinition definition = new ResourceDefinition(); definition.setId(id); definition.setName(name); return definition; } public Map> getI18nMessages() { if (org.springframework.util.CollectionUtils.isEmpty(i18nMessages)) { this.i18nMessages = I18nSupportUtils .putI18nMessages( resolvePermissionPrefix + this.id, "name", supportLocale, null, this.i18nMessages ); } return i18nMessages; } public ResourceDefinition copy() { ResourceDefinition definition = FastBeanCopier.copy(this, ResourceDefinition::new); definition.setActions(actions.stream().map(ResourceActionDefinition::copy).collect(Collectors.toSet())); return definition; } public ResourceDefinition addAction(String id, String name) { ResourceActionDefinition action = new ResourceActionDefinition(); action.setId(id); action.setName(name); return addAction(action); } public synchronized ResourceDefinition addAction(ResourceActionDefinition action) { actionIds = null; actions.add(action); return this; } public Optional getAction(String action) { return actions.stream() .filter(act -> act.getId().equalsIgnoreCase(action)) .findAny(); } public Set getActionIds() { if (actionIds == null) { actionIds = this.actions .stream() .map(ResourceActionDefinition::getId) .collect(Collectors.toSet()); } return actionIds; } @JsonIgnore public List getDataAccessAction() { return actions.stream() .filter(act -> CollectionUtils.isNotEmpty(act.getDataAccess().getDataAccessTypes())) .collect(Collectors.toList()); } public boolean hasDataAccessAction() { return actions.stream() .anyMatch(act -> CollectionUtils.isNotEmpty(act.getDataAccess().getDataAccessTypes())); } public boolean hasAction(Collection actions) { if (CollectionUtils.isEmpty(this.actions)) { return true; } if (CollectionUtils.isEmpty(actions)) { return false; } if (logical == Logical.AND) { return getActionIds().containsAll(actions); } return getActionIds().stream().anyMatch(actions::contains); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourcesDefinition.java ================================================ package org.hswebframework.web.authorization.define; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.annotation.Logical; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @Getter @Setter public class ResourcesDefinition { private final Set resources = ConcurrentHashMap.newKeySet(); private Logical logical = Logical.DEFAULT; private Phased phased = Phased.before; public void clear() { resources.clear(); } public void addResource(ResourceDefinition resource, boolean merge) { ResourceDefinition definition = getResource(resource.getId()).orElse(null); if (definition != null) { if (merge) { resource.getActions() .stream() .map(ResourceActionDefinition::copy) .forEach(definition::addAction); } else { resources.remove(definition); } } resources.add(resource.copy()); } public Optional getResource(String id) { return resources .stream() .filter(resource -> resource.getId().equals(id)) .findAny(); } @JsonIgnore public List getDataAccessResources() { return resources .stream() .filter(ResourceDefinition::hasDataAccessAction) .collect(Collectors.toList()); } public boolean hasPermission(Permission permission) { if (CollectionUtils.isEmpty(resources)) { return true; } return getResource(permission.getId()) .filter(resource -> resource.hasAction(permission.getActions())) .isPresent(); } public boolean isEmpty() { return resources.isEmpty(); } public boolean hasPermission(Authentication authentication) { int size = resources.size(); if (size == 0) { return true; } if (size == 1) { for (ResourceDefinition resource : resources) { if (authentication.hasPermission(resource.getId(), resource.getActionIds())) { return true; } } return false; } if (logical == Logical.AND) { return resources .stream() .allMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds())); } return resources .stream() .anyMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds())); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionManager.java ================================================ package org.hswebframework.web.authorization.dimension; import reactor.core.publisher.Flux; import java.util.Collection; /** * 维度管理器 * * @author zhouhao * @since 4.0.12 */ public interface DimensionManager { /** * 获取用户维度 * * @param userId 用户ID * @return 用户维度信息 */ Flux getUserDimension(Collection userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserBind.java ================================================ package org.hswebframework.web.authorization.dimension; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class DimensionUserBind implements Externalizable { private static final long serialVersionUID = -6849794470754667710L; private String userId; private String dimensionType; private String dimensionId; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(userId); out.writeUTF(dimensionType); out.writeUTF(dimensionId); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { userId = in.readUTF(); dimensionType = in.readUTF(); dimensionId = in.readUTF(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserBindProvider.java ================================================ package org.hswebframework.web.authorization.dimension; import reactor.core.publisher.Flux; import java.util.Collection; public interface DimensionUserBindProvider { Flux getDimensionBindInfo(Collection userIdList); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserDetail.java ================================================ package org.hswebframework.web.authorization.dimension; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.authorization.Dimension; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class DimensionUserDetail implements Serializable { private static final long serialVersionUID = -6849794470754667710L; private String userId; private List dimensions; public DimensionUserDetail merge(DimensionUserDetail detail) { DimensionUserDetail newDetail = new DimensionUserDetail(); newDetail.setUserId(userId); newDetail.setDimensions(new ArrayList<>()); if (null != dimensions) { newDetail.dimensions.addAll(dimensions); } if (null != detail.getDimensions()) { newDetail.dimensions.addAll(detail.getDimensions()); } return newDetail; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AbstractAuthorizationEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.event.DefaultAsyncEvent; import java.util.Optional; import java.util.function.Function; /** * 抽象授权事件,保存事件常用的数据 * * @author zhouhao * @since 3.0 */ public abstract class AbstractAuthorizationEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -3027505108916079214L; protected String username; protected String password; private final transient Function parameterGetter; /** * 所有参数不能为null * * @param username 用户名 * @param password 密码 * @param parameterGetter 参数获取函数,用户获取授权时传入的参数 */ public AbstractAuthorizationEvent(String username, String password, Function parameterGetter) { if (username == null || password == null || parameterGetter == null) { throw new NullPointerException(); } this.username = username; this.password = password; this.parameterGetter = parameterGetter; } @SuppressWarnings("unchecked") public Optional getParameter(String name) { return Optional.ofNullable((T) parameterGetter.apply(name)); } public String getUsername() { return username; } public String getPassword() { return password; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationBeforeEvent.java ================================================ /* * 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.authorization.events; import lombok.Getter; import org.hswebframework.web.authorization.Authentication; import java.util.function.Function; /** * 授权前事件 * * @author zhouhao * @since 3.0 */ @Getter public class AuthorizationBeforeEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = 5948747533500518524L; private String userId; private Authentication authentication; public AuthorizationBeforeEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public void setAuthorized(String userId) { this.userId = userId; } public void setAuthorized(Authentication authentication) { this.authentication = authentication; } public boolean isAuthorized() { return userId != null || authentication != null; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationDecodeEvent.java ================================================ /* * 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.authorization.events; import java.util.function.Function; /** * 在进行授权时的最开始,触发此事件进行用户名密码解码,解码后请调用{@link #setUsername(String)} {@link #setPassword(String)}重新设置用户名密码 * * @author zhouhao * @since 3.0 */ public class AuthorizationDecodeEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = 5418501934490174251L; public AuthorizationDecodeEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public void setUsername(String username) { super.username = username; } public void setPassword(String password) { super.password = password; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationEvent.java ================================================ /* * 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.authorization.events; /** * 授权事件 * * @author zhouhao * @see AuthorizationSuccessEvent * @see AuthorizationFailedEvent * @see AuthorizationBeforeEvent * @see AuthorizationDecodeEvent * @see AuthorizationExitEvent * @see org.springframework.context.ApplicationEvent * @since 3.0 */ public interface AuthorizationEvent { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationExitEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; import org.springframework.context.ApplicationEvent; /** * 退出登录事件 * * @author zhouhao */ public class AuthorizationExitEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -4590245933665047280L; private final Authentication authentication; public AuthorizationExitEvent(Authentication authentication) { this.authentication = authentication; } public Authentication getAuthentication() { return authentication; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationFailedEvent.java ================================================ /* * 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.authorization.events; import java.util.function.Function; /** * 授权失败时触发 * * @author zhouhao */ public class AuthorizationFailedEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = -101792832265740828L; /** * 异常信息 */ private Throwable exception; public AuthorizationFailedEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public Throwable getException() { return exception; } public void setException(Throwable exception) { this.exception = exception; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationInitializeEvent.java ================================================ package org.hswebframework.web.authorization.events; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; @Getter @Setter @AllArgsConstructor public class AuthorizationInitializeEvent extends DefaultAsyncEvent { private Authentication authentication; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationSuccessEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; /** * 授权成功事件,当授权成功时,触发此事件,并传入授权的信息 * * @author zhouhao * @see Authentication * @since 3.0 */ public class AuthorizationSuccessEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -2452116314154155058L; private final Authentication authentication; private final transient Function parameterGetter; private Map result = new HashMap<>(); public AuthorizationSuccessEvent(Authentication authentication, Function parameterGetter) { this.authentication = authentication; this.parameterGetter = parameterGetter; } public Authentication getAuthentication() { return authentication; } @SuppressWarnings("unchecked") public Optional getParameter(String name) { return Optional.ofNullable((T) parameterGetter.apply(name)); } public Map getResult() { return result; } public void setResult(Map result) { this.result = result; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java ================================================ package org.hswebframework.web.authorization.events; import org.hswebframework.web.authorization.define.AuthorizingContext; import org.hswebframework.web.authorization.define.HandleType; import org.hswebframework.web.event.DefaultAsyncEvent; import org.springframework.context.ApplicationEvent; /** * 权限控制事件,在进行权限控制之前会推送此事件,用于自定义权限控制结果: * {@code * @EventListener * public void handleAuthEvent(AuthorizingHandleBeforeEvent e) { * //admin用户可以访问全部操作 * if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) { * e.setAllow(true); * } * } * } * * @author zhouhao * @since 4.0 */ public class AuthorizingHandleBeforeEvent extends DefaultAsyncEvent implements AuthorizationEvent { private boolean allow = false; private boolean execute = true; private String message; private final AuthorizingContext context; /** * @deprecated 数据权限控制已取消,4.1版本后移除 */ @Deprecated private final HandleType handleType; public AuthorizingHandleBeforeEvent(AuthorizingContext context, HandleType handleType) { this.context = context; this.handleType = handleType; } public AuthorizingContext getContext() { return context; } public boolean isExecute() { return execute; } public boolean isAllow() { return allow; } /** * 设置通过当前请求 * * @param allow allow */ public void setAllow(boolean allow) { execute = false; this.allow = allow; } public String getMessage() { return message; } /** * 设置错误提示消息 * * @param message 消息 */ public void setMessage(String message) { this.message = message; } /** * @return 权限控制类型 */ public HandleType getHandleType() { return handleType; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AccessDenyException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; import org.hswebframework.web.exception.I18nSupportException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.Set; /** * 权限验证异常 * * @author zhouhao * @since 3.0 */ @ResponseStatus(HttpStatus.FORBIDDEN) @Getter public class AccessDenyException extends I18nSupportException { private static final long serialVersionUID = -5135300127303801430L; private String code; public AccessDenyException() { this("error.access_denied"); } public AccessDenyException(String message) { super(message); } public AccessDenyException(String permission, Set actions) { super("error.permission_denied", permission, actions); } public AccessDenyException(String message, String code) { this(message, code, null); } public AccessDenyException(String message, Throwable cause) { this(message, "access_denied", cause); } public AccessDenyException(String message, String code, Throwable cause) { super(message, cause, code); this.code = code; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends AccessDenyException { public NoStackTrace() { super(); } public NoStackTrace(String message) { super(message); } public NoStackTrace(String permission, Set actions) { super(permission, actions); } public NoStackTrace(String message, String code) { super(message, code); } public NoStackTrace(String message, Throwable cause) { super(message, cause); } public NoStackTrace(String message, String code, Throwable cause) { super(message, code, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AuthenticationException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; import org.hswebframework.web.exception.I18nSupportException; @Getter public class AuthenticationException extends I18nSupportException { public static String ILLEGAL_PASSWORD = "illegal_password"; public static String USER_DISABLED = "user_disabled"; private final String code; public AuthenticationException(String code) { this(code, "error." + code); } public AuthenticationException(String code, String message) { super(message); this.code = code; } public AuthenticationException(String code, String message, Throwable cause) { super(message, cause); this.code = code; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends AuthenticationException { public NoStackTrace(String code) { super(code); } public NoStackTrace(String code, String message) { super(code, message); } public NoStackTrace(String code, String message, Throwable cause) { super(code, message, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/NeedTwoFactorException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; /** * @author zhouhao * @since 3.0.4 */ @Getter public class NeedTwoFactorException extends RuntimeException { private static final long serialVersionUID = 3655980280834947633L; private String provider; public NeedTwoFactorException(String message, String provider) { super(message); this.provider = provider; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/UnAuthorizedException.java ================================================ /* * * * 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.authorization.exception; import lombok.Getter; import org.hswebframework.web.authorization.token.TokenState; import org.hswebframework.web.exception.I18nSupportException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; /** * 未授权异常 * * @author zhouhao * @since 3.0 */ @Getter @ResponseStatus(HttpStatus.UNAUTHORIZED) public class UnAuthorizedException extends I18nSupportException { private static final long serialVersionUID = 2422918455013900645L; private final TokenState state; public UnAuthorizedException() { this(TokenState.expired); } public UnAuthorizedException(TokenState state) { this(state.getText(), state); } public UnAuthorizedException(String message, TokenState state) { super(message); this.state = state; } public UnAuthorizedException(String message, TokenState state, Throwable cause) { super(message, cause); this.state = state; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends UnAuthorizedException { public NoStackTrace() { super(); } public NoStackTrace(TokenState state) { super(state); } public NoStackTrace(String message, TokenState state) { super(message, state); } public NoStackTrace(String message, TokenState state, Throwable cause) { super(message, state, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingNullValueHolder.java ================================================ package org.hswebframework.web.authorization.setting; import java.util.List; import java.util.Optional; /** * @author zhouhao * @since 1.0.0 */ public class SettingNullValueHolder implements SettingValueHolder { public static final SettingNullValueHolder INSTANCE = new SettingNullValueHolder(); private SettingNullValueHolder() { } @Override public Optional> asList(Class t) { return Optional.empty(); } @Override public Optional as(Class t) { return Optional.empty(); } @Override public Optional asString() { return Optional.empty(); } @Override public Optional asLong() { return Optional.empty(); } @Override public Optional asInt() { return Optional.empty(); } @Override public Optional asDouble() { return Optional.empty(); } @Override public Optional getValue() { return Optional.empty(); } @Override public UserSettingPermission getPermission() { return UserSettingPermission.NONE; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingValueHolder.java ================================================ package org.hswebframework.web.authorization.setting; import java.util.List; import java.util.Optional; public interface SettingValueHolder { SettingValueHolder NULL = SettingNullValueHolder.INSTANCE; Optional> asList(Class t); Optional as(Class t); Optional asString(); Optional asLong(); Optional asInt(); Optional asDouble(); Optional getValue(); UserSettingPermission getPermission(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/StringSourceSettingHolder.java ================================================ package org.hswebframework.web.authorization.setting; import com.alibaba.fastjson.JSON; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.utils.StringUtils; import org.hswebframework.web.dict.EnumDict; import java.util.List; import java.util.Optional; /** * @author zhouhao * @since 3.0.4 */ @AllArgsConstructor @Getter public class StringSourceSettingHolder implements SettingValueHolder { private String value; private UserSettingPermission permission; public static SettingValueHolder of(String value, UserSettingPermission permission) { if (value == null) { return SettingValueHolder.NULL; } return new StringSourceSettingHolder(value, permission); } @Override public Optional> asList(Class t) { return getNativeValue() .map(v -> JSON.parseArray(v, t)); } protected T convert(String value, Class t) { if (t.isEnum()) { if (EnumDict.class.isAssignableFrom(t)) { T val = (T) EnumDict.find((Class) t, value).orElse(null); if (null != val) { return val; } } for (T enumConstant : t.getEnumConstants()) { if (((Enum) enumConstant).name().equalsIgnoreCase(value)) { return enumConstant; } } } return JSON.parseObject(value, t); } @Override @SuppressWarnings("all") public Optional as(Class t) { if (t == String.class) { return (Optional) asString(); } else if (Long.class == t || long.class == t) { return (Optional) asLong(); } else if (Integer.class == t || int.class == t) { return (Optional) asInt(); } else if (Double.class == t || double.class == t) { return (Optional) asDouble(); } return getNativeValue().map(v -> convert(v, t)); } @Override public Optional asString() { return getNativeValue(); } @Override public Optional asLong() { return getNativeValue().map(StringUtils::toLong); } @Override public Optional asInt() { return getNativeValue().map(StringUtils::toInt); } @Override public Optional asDouble() { return getNativeValue().map(StringUtils::toDouble); } private Optional getNativeValue() { return Optional.ofNullable(value); } @Override public Optional getValue() { return Optional.ofNullable(value); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingManager.java ================================================ package org.hswebframework.web.authorization.setting; /** * @author zhouhao * @since 3.0.4 */ public interface UserSettingManager { SettingValueHolder getSetting(String userId, String key); void saveSetting(String userId, String key, String value, UserSettingPermission permission); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingPermission.java ================================================ package org.hswebframework.web.authorization.setting; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.web.dict.Dict; import org.hswebframework.web.dict.EnumDict; /** * @author zhouhao * @since 3.0.4 */ @AllArgsConstructor @Getter @Dict("user-setting-permission") public enum UserSettingPermission implements EnumDict { NONE("无"), R("读"), W("写"), RW("读写"); private String text; @Override public String getValue() { return name(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/AbstractDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.DataAccessConfig; /** * @author zhouhao * @see DataAccessConfig * @since 3.0 */ public abstract class AbstractDataAccessConfig implements DataAccessConfig { private static final long serialVersionUID = -9025349704771557106L; private String action; @Override public String getAction() { return action; } public void setAction(String action) { this.action = action; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/CompositeReactiveAuthenticationManager.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hswebframework.web.authorization.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; @AllArgsConstructor @Slf4j public class CompositeReactiveAuthenticationManager implements ReactiveAuthenticationManager { private final List providers; @Override public Mono authenticate(Mono request) { return Flux .concat( providers .stream() .map(manager -> manager .authenticate(request) .onErrorResume((err) -> { log.warn("get user authenticate error", err); return Mono.empty(); })) .collect(Collectors.toList())) .take(1) .next(); } @Override public Mono getByUserId(String userId) { if (providers.size() == 1) { return providers.get(0).getByUserId(userId); } return Flux .fromStream(providers .stream() .map(manager -> manager .getByUserId(userId) .onErrorResume((err) -> { log.warn("get user [{}] authentication error", userId, err); return Mono.empty(); }) )) .flatMap(Function.identity()) .as(AuthenticationUtils::merge); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.*; import org.hswebframework.web.authorization.builder.AuthenticationBuilderFactory; import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory; import org.hswebframework.web.authorization.dimension.DimensionManager; import org.hswebframework.web.authorization.dimension.DimensionUserBindProvider; 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.*; import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager; import org.hswebframework.web.authorization.twofactor.defaults.DefaultTwoFactorValidatorManager; import org.hswebframework.web.convert.CustomMessageConverter; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; /** * @author zhouhao */ @AutoConfiguration public class DefaultAuthorizationAutoConfiguration { @Bean @ConditionalOnMissingBean(UserTokenManager.class) @ConfigurationProperties(prefix = "hsweb.user-token") public UserTokenManager userTokenManager() { return new DefaultUserTokenManager(); } @Bean @ConditionalOnMissingBean // @ConditionalOnBean(ReactiveAuthenticationManagerProvider.class) public ReactiveAuthenticationManager reactiveAuthenticationManager(List providers) { return new CompositeReactiveAuthenticationManager(providers); } @Bean @ConditionalOnBean(ReactiveAuthenticationManager.class) public UserTokenReactiveAuthenticationSupplier userTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager, ReactiveAuthenticationManager authenticationManager) { UserTokenReactiveAuthenticationSupplier supplier = new UserTokenReactiveAuthenticationSupplier(userTokenManager, authenticationManager); ReactiveAuthenticationHolder.addSupplier(supplier); return supplier; } @Bean @ConditionalOnBean(AuthenticationManager.class) public UserTokenAuthenticationSupplier userTokenAuthenticationSupplier(UserTokenManager userTokenManager, AuthenticationManager authenticationManager) { UserTokenAuthenticationSupplier supplier = new UserTokenAuthenticationSupplier(userTokenManager, authenticationManager); AuthenticationHolder.addSupplier(supplier); return supplier; } @Bean @ConditionalOnMissingBean(DataAccessConfigBuilderFactory.class) @ConfigurationProperties(prefix = "hsweb.authorization.data-access", ignoreInvalidFields = true) public SimpleDataAccessConfigBuilderFactory dataAccessConfigBuilderFactory() { return new SimpleDataAccessConfigBuilderFactory(); } @Bean @ConditionalOnMissingBean(AuthenticationBuilderFactory.class) public AuthenticationBuilderFactory authenticationBuilderFactory(DataAccessConfigBuilderFactory dataAccessConfigBuilderFactory) { return new SimpleAuthenticationBuilderFactory(dataAccessConfigBuilderFactory); } @Bean public CustomMessageConverter authenticationCustomMessageConverter(AuthenticationBuilderFactory factory) { return new CustomMessageConverter() { @Override public boolean support(Class clazz) { return clazz == Authentication.class; } @Override public Object convert(Class clazz, byte[] message) { String json = new String(message); return factory.create().json(json).build(); } }; } @Bean @ConditionalOnMissingBean(DimensionManager.class) public DimensionManager defaultDimensionManager(ObjectProviderbindProviders, ObjectProvider providers){ DefaultDimensionManager manager = new DefaultDimensionManager(); bindProviders.forEach(manager::addBindProvider); providers.forEach(manager::addProvider); return manager; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultDimensionManager.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionProvider; import org.hswebframework.web.authorization.dimension.DimensionManager; import org.hswebframework.web.authorization.dimension.DimensionUserBind; import org.hswebframework.web.authorization.dimension.DimensionUserBindProvider; import org.hswebframework.web.authorization.dimension.DimensionUserDetail; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; import java.util.stream.Collectors; public class DefaultDimensionManager implements DimensionManager { private final List dimensionProviders = new CopyOnWriteArrayList<>(); private final List bindProviders = new CopyOnWriteArrayList<>(); private final Mono> providerMapping = Flux .defer(() -> Flux.fromIterable(dimensionProviders)) .flatMap(provider -> provider .getAllType() .map(type -> Tuples.of(type.getId(), provider))) .collectMap(Tuple2::getT1, Tuple2::getT2); public DefaultDimensionManager() { } public void addProvider(DimensionProvider provider) { dimensionProviders.add(provider); } public void addBindProvider(DimensionUserBindProvider bindProvider) { bindProviders.add(bindProvider); } private Mono> providerMapping() { return providerMapping; } @Override public Flux getUserDimension(Collection userId) { return this .providerMapping() .flatMapMany(providerMapping -> Flux .fromIterable(bindProviders) //获取绑定信息 .flatMap(provider -> provider.getDimensionBindInfo(userId)) .groupBy(DimensionUserBind::getDimensionType) .flatMap(group -> { String type = group.key(); Flux binds = group.cache(); DimensionProvider provider = providerMapping.get(type); if (null == provider) { return Mono.empty(); } //获取维度信息 return binds .map(DimensionUserBind::getDimensionId) .collect(Collectors.toSet()) .flatMapMany(idList -> provider.getDimensionsById(SimpleDimensionType.of(type), idList)) .collectMap(Dimension::getId, Function.identity()) .flatMapMany(mapping -> binds .groupBy(DimensionUserBind::getUserId) .flatMap(userGroup -> Mono .zip( Mono.just(userGroup.key()), userGroup .handle((bind, sink) -> { Dimension dimension = mapping.get(bind.getDimensionId()); if (dimension != null) { sink.next(dimension); } }) .collectList(), DimensionUserDetail::of )) ); }) ) .groupBy(DimensionUserDetail::getUserId) .flatMap(group->group.reduce(DimensionUserDetail::merge)); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DimensionDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DefaultDataAccessType; import org.hswebframework.web.authorization.access.ScopeDataAccessConfig; import org.hswebframework.web.authorization.simple.AbstractDataAccessConfig; import java.util.Set; @Getter @Setter @EqualsAndHashCode(callSuper = true) public class DimensionDataAccessConfig extends AbstractDataAccessConfig implements ScopeDataAccessConfig { private Set scope; private boolean children; /** * @see DimensionType#getId() */ private String scopeType; @Override public DefaultDataAccessType getType() { return DefaultDataAccessType.DIMENSION_SCOPE; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/PlainTextUsernamePasswordAuthenticationRequest.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.authorization.AuthenticationRequest; /** * @author zhouhao * @since 3.0.0-RC */ @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class PlainTextUsernamePasswordAuthenticationRequest implements AuthenticationRequest { private String username; private String password; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java ================================================ /* * 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.authorization.simple; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.*; import java.io.Serial; import java.io.Serializable; import java.util.*; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; public class SimpleAuthentication implements Authentication { static final AtomicLongFieldUpdater ACCESS_COUNT_UPDATER = AtomicLongFieldUpdater.newUpdater(SimpleAuthentication.class, "accessCount"); @Serial private static final long serialVersionUID = -2898863220255336528L; @Getter private User user; @Setter private List permissions = new ArrayList<>(); private List dimensions = new ArrayList<>(); @Setter private Map attributes = new HashMap<>(); public static Authentication of() { return new SimpleAuthentication(); } @Override @SuppressWarnings("unchecked") public Optional getAttribute(String name) { return Optional.ofNullable((T) attributes.get(name)); } public List getDimensions() { return dimensions == null ? Collections.emptyList() : dimensions; } public List getPermissions() { return permissions == null ? Collections.emptyList() : permissions; } @Override public Map getAttributes() { return attributes == null ? Collections.emptyMap() : attributes; } public SimpleAuthentication merge(Authentication authentication) { Map mePermissionGroup = permissions .stream() .collect(Collectors.toMap(Permission::getId, Function.identity())); if (authentication.getUser() != null) { user = authentication.getUser(); } this.attributes = new HashMap<>(getAttributes()); this.attributes.putAll(authentication.getAttributes()); this.permissions = new ArrayList<>(this.getPermissions()); for (Permission permission : authentication.getPermissions()) { Permission me = mePermissionGroup.get(permission.getId()); if (me == null) { permissions.add(permission.copy()); continue; } me.getActions().addAll(permission.getActions()); } this.dimensions = new ArrayList<>(this.getDimensions()); for (Dimension dimension : authentication.getDimensions()) { if (getDimension(dimension.getType(), dimension.getId()).isEmpty()) { dimensions.add(dimension); } } return this; } protected SimpleAuthentication newInstance() { return new SimpleAuthentication(); } @Override public Authentication copy(BiPredicate permissionFilter, Predicate dimension) { SimpleAuthentication authentication = newInstance(); 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()) ); if (user != null) { authentication.setUser0(user); } authentication.setAttributes(new HashMap<>(attributes)); return authentication; } public void setUser(User user) { this.user = user; dimensions.add(user); } protected void setUser0(User user) { this.user = user; } public void setDimensions(List dimensions) { this.dimensions.addAll(dimensions); } public void setDimensions(Collection dimensions) { this.dimensions.addAll(dimensions); } public void addDimension(Dimension dimension) { this.dimensions.add(dimension); } private transient volatile Map> dimensionMapping; private transient volatile Map permissionMapping; private transient volatile long accessCount; protected boolean fastPath() { // 总共访问超过8次,则进行初始化缓存. if (ACCESS_COUNT_UPDATER.incrementAndGet(this) == 8) { if (permissionMapping == null) { permissionMapping = permissions == null ? Collections.emptyMap() : permissions .stream() .collect(Collectors .toMap(Permission::getId, Function.identity(), (a, b) -> b)); dimensionMapping = dimensions == null ? Collections.emptyMap() : dimensions .stream() .collect(Collectors .groupingBy(d -> d.getType().getId(), Collectors.toMap( Dimension::getId, Function.identity(), (a, b) -> a))); } } return permissionMapping != null; } @Override public boolean hasPermission(String permissionId, Collection actions) { Map permissionMapping = this.permissionMapping; if (fastPath() && permissionMapping != null) { Permission permission = permissionMapping.get(permissionId); if (permission == null) { permission = permissionMapping.get("*"); } if (permission == null) { return false; } return actions.isEmpty() || permission.getActions().containsAll(actions) || permission.getActions().contains("*"); } return Authentication.super.hasPermission(permissionId, actions); } @Override public Optional getDimension(String type, String id) { Map> dimensionMapping = this.dimensionMapping; if (fastPath() && dimensionMapping != null) { Map mapping = dimensionMapping.get(type); if (mapping == null) { return Optional.empty(); } return Optional.ofNullable(mapping.get(id)); } return Authentication.super.getDimension(type, id); } @Override public Optional getDimension(DimensionType type, String id) { return getDimension(type.getId(), id); } @Override public List getDimensions(DimensionType type) { return this.getDimensions(type.getId()); } @Override public List getDimensions(String type) { Map> dimensionMapping = this.dimensionMapping; if (fastPath() && dimensionMapping != null) { Map mapping = dimensionMapping.get(type); if (mapping == null) { return List.of(); } return new ArrayList<>(mapping.values()); } return Authentication.super.getDimensions(type); } @Override public Optional getPermission(String id) { Map permissionMapping = this.permissionMapping; if (fastPath() && permissionMapping != null) { return Optional.ofNullable(permissionMapping.get(id)); } return Authentication.super.getPermission(id); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleDimension.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionType; import java.util.Map; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor @EqualsAndHashCode public class SimpleDimension implements Dimension { private String id; private String name; private DimensionType type; private Map options; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleDimensionType.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.DimensionType; import java.io.Serializable; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor @EqualsAndHashCode public class SimpleDimensionType implements DimensionType, Serializable { private static final long serialVersionUID = -6849794470754667710L; private String id; private String name; public static SimpleDimensionType of(String id) { return of(id, id); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleFieldFilterDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DefaultDataAccessType; import org.hswebframework.web.authorization.access.FieldFilterDataAccessConfig; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import static org.hswebframework.web.authorization.access.DataAccessConfig.DefaultType.DENY_FIELDS; /** * 默认配置实现 * * @author zhouhao * @see FieldFilterDataAccessConfig * @since 3.0 */ public class SimpleFieldFilterDataAccessConfig extends AbstractDataAccessConfig implements FieldFilterDataAccessConfig { private static final long serialVersionUID = 8080660575093151866L; private Set fields; public SimpleFieldFilterDataAccessConfig() { } public SimpleFieldFilterDataAccessConfig(String... fields) { this.fields = new HashSet<>(Arrays.asList(fields)); } @Override public Set getFields() { return fields; } public void setFields(Set fields) { this.fields = fields; } @Override public DataAccessType getType() { return DefaultDataAccessType.FIELD_DENY; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleOwnCreatedDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.OwnCreatedDataAccessConfig; /** * @author zhouhao * @since 3.0 */ public class SimpleOwnCreatedDataAccessConfig extends AbstractDataAccessConfig implements OwnCreatedDataAccessConfig { private static final long serialVersionUID = -6059330812806119730L; public SimpleOwnCreatedDataAccessConfig() { } public SimpleOwnCreatedDataAccessConfig(String action) { setAction(action); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.apache.commons.collections4.CollectionUtils; 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 */ @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(exclude = "dataAccesses") public class SimplePermission implements Permission { private static final long serialVersionUID = 7587266693680162184L; private String id; private String name; private Set actions; private Set dataAccesses; private Map options; public Set getActions() { if (actions == null) { actions = new java.util.HashSet<>(); } return actions; } public Set getDataAccesses() { if (dataAccesses == null) { dataAccesses = new java.util.HashSet<>(); } return dataAccesses; } @Override public Permission copy(Predicate actionFilter, Predicate dataAccessFilter) { SimplePermission permission = new SimplePermission(); permission.setId(id); permission.setName(name); 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); } @Override public String toString() { return id + (CollectionUtils.isNotEmpty(actions) ? ":" + String.join(",", actions) : ""); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleRole.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.Role; import java.io.Serializable; import java.util.Map; /** * @author zhouhao */ @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode public class SimpleRole implements Role { private static final long serialVersionUID = 7460859165231311347L; private String id; private String name; private Map options; public static Role of(Dimension dimension) { return SimpleRole.builder() .name(dimension.getName()) .id(dimension.getId()) .options(dimension.getOptions()) .build(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleUser.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.User; import java.io.Serial; import java.io.Serializable; import java.util.Map; /** * @author zhouhao */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @EqualsAndHashCode public class SimpleUser implements User { @Serial private static final long serialVersionUID = 2194541828191869091L; private String id; private String username; private String name; private String userType; private Map options; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/builder/DataAccessConfigConverter.java ================================================ package org.hswebframework.web.authorization.simple.builder; import org.hswebframework.web.authorization.access.DataAccessConfig; /** * @author zhouhao */ public interface DataAccessConfigConverter { boolean isSupport(String type, String action, String config); DataAccessConfig convert(String type, String action, String config); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/builder/SimpleAuthenticationBuilder.java ================================================ package org.hswebframework.web.authorization.simple.builder; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Maps; import org.hswebframework.web.authorization.*; import org.hswebframework.web.authorization.builder.AuthenticationBuilder; import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory; import org.hswebframework.web.authorization.simple.*; import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; /** * @author zhouhao */ public class SimpleAuthenticationBuilder implements AuthenticationBuilder { private SimpleAuthentication authentication = new SimpleAuthentication(); private DataAccessConfigBuilderFactory dataBuilderFactory; public SimpleAuthenticationBuilder(DataAccessConfigBuilderFactory dataBuilderFactory) { this.dataBuilderFactory = dataBuilderFactory; } public void setDataBuilderFactory(DataAccessConfigBuilderFactory dataBuilderFactory) { this.dataBuilderFactory = dataBuilderFactory; } @Override public AuthenticationBuilder user(User user) { Objects.requireNonNull(user); authentication.setUser(user); return this; } @Override public AuthenticationBuilder user(String user) { return user(JSON.parseObject(user, SimpleUser.class)); } @Override public AuthenticationBuilder user(Map user) { Objects.requireNonNull(user.get("id")); user(SimpleUser.builder() .id(user.get("id")) .username(user.get("username")) .name(user.get("name")) .userType(user.get("type")) .build()); return this; } @Override public AuthenticationBuilder role(List role) { authentication.getDimensions().addAll(role); return this; } @Override @SuppressWarnings("unchecked") public AuthenticationBuilder role(String role) { return role((List) JSON.parseArray(role, SimpleRole.class)); } @Override public AuthenticationBuilder permission(List permission) { authentication.setPermissions(permission); return this; } public AuthenticationBuilder permission(JSONArray jsonArray) { List permissions = new ArrayList<>(); for (int i = 0; i < jsonArray.size(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); SimplePermission permission = new SimplePermission(); permission.setId(jsonObject.getString("id")); permission.setName(jsonObject.getString("name")); permission.setOptions(jsonObject.getJSONObject("options")); JSONArray actions = jsonObject.getJSONArray("actions"); if (actions != null) { permission.setActions(new HashSet<>(actions.toJavaList(String.class))); } JSONArray dataAccess = jsonObject.getJSONArray("dataAccesses"); if (null != dataAccess) { permission.setDataAccesses(dataAccess.stream().map(JSONObject.class::cast) .map(dataJson -> dataBuilderFactory .create() .fromJson(dataJson.toJSONString()) .build()) .filter(Objects::nonNull) .collect(Collectors.toSet())); } permissions.add(permission); } authentication.setPermissions(permissions); return this; } @Override public AuthenticationBuilder permission(String permissionJson) { return permission(JSON.parseArray(permissionJson)); } @Override public AuthenticationBuilder attributes(String attributes) { authentication.getAttributes().putAll(JSON.>parseObject(attributes, Map.class)); return this; } @Override public AuthenticationBuilder attributes(Map permission) { authentication.getAttributes().putAll(permission); return this; } public AuthenticationBuilder dimension(JSONArray json) { if (json == null) { return this; } List dimensions = new ArrayList<>(); for (int i = 0; i < json.size(); i++) { JSONObject jsonObject = json.getJSONObject(i); Object type = jsonObject.get("type"); Map
* ⚠️:任何时候都不应该对返回的Set进行写操作 * * @return 如果没有配置返回空{@link Collections#emptySet()},不会返回null. * @see DataAccessConfig * @see org.hswebframework.web.authorization.access.DataAccessController */ @Deprecated Set getDataAccesses(); default Set getDataAccesses(String action) { return getDataAccesses() .stream() .filter(conf -> conf.getAction().equals(action)) .collect(Collectors.toSet()); } /** * 查找数据权限配置 * * @param configPredicate 数据权限配置匹配规则 * @param 数据权限配置类型 * @return {@link Optional} * @see this#scope(String, String, String) */ @SuppressWarnings("all") default Optional findDataAccess(DataAccessPredicate configPredicate) { return (Optional) getDataAccesses().stream() .filter(configPredicate) .findFirst(); } /** * 查找字段过滤的数据权限配置(列级数据权限),比如:不查询某些字段 * * @param action 权限操作类型 {@link Permission#ACTION_QUERY} * @return {@link Optional} * @see FieldFilterDataAccessConfig * @see FieldFilterDataAccessConfig#getFields() */ default Optional findFieldFilter(String action) { return findDataAccess(conf -> conf instanceof FieldFilterDataAccessConfig && conf.getAction().equals(action)); } /** * 获取不能执行操作的字段 * * @param action 权限操作 * @return 未配置时返回空set, 不会返回null */ default Set findDenyFields(String action) { return findFieldFilter(action) .filter(conf -> DENY_FIELDS.equals(conf.getType().getId())) .map(FieldFilterDataAccessConfig::getFields) .orElseGet(Collections::emptySet); } /** * 查找数据范围权限控制配置(行级数据权限),比如: 只能查询本机构的数据 * * @param type 范围类型标识,由具体的实现定义,如: 机构范围 * @param scopeType 范围类型,由具体的实现定义,如: 只能查看自己所在机构 * @param action 权限操作 {@link Permission#ACTION_QUERY} * @return 未配置时返回空set, 不会返回null */ default Set findScope(String action, String type, String scopeType) { return findScope(scope(action, type, scopeType)); } default Set findScope(Permission.DataAccessPredicate predicate) { return findDataAccess(predicate) .map(ScopeDataAccessConfig::getScope) .orElseGet(Collections::emptySet); } /** * 构造一个数据范围权限控制配置查找逻辑 * * @param type 范围类型标识,由具体的实现定义,如: 机构范围 * @param scopeType 范围类型,由具体的实现定义,如: 只能查看自己所在机构 * @param action 权限操作 {@link Permission#ACTION_QUERY} * @return {@link DataAccessPredicate} */ static Permission.DataAccessPredicate scope(String action, String type, String scopeType) { Objects.requireNonNull(action, "action can not be null"); Objects.requireNonNull(type, "type can not be null"); Objects.requireNonNull(scopeType, "scopeType can not be null"); return config -> config instanceof ScopeDataAccessConfig && action.equals(config.getAction()) && type.equals(config.getType()) && scopeType.equals(((ScopeDataAccessConfig) config).getScopeType()); } Permission copy(); Permission copy(Predicate actionFilter,Predicate dataAccessFilter); /** * 数据权限查找判断逻辑接口 * * @param */ interface DataAccessPredicate extends Predicate { boolean test(DataAccessConfig config); @Override default DataAccessPredicate and(Predicate super DataAccessConfig> other) { return (t) -> test(t) && other.test(t); } @Override default DataAccessPredicate or(Predicate super DataAccessConfig> other) { return (t) -> test(t) || other.test(t); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationHolder.java ================================================ /* * Copyright 2019 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.authorization; import com.google.common.collect.Lists; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.context.Context; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; /** * 响应式权限保持器,用于响应式方式获取当前登录用户的权限信息. * 例如: * {@code * @RequestMapping("/example") * public Mono example(){ * return ReactiveAuthenticationHolder.get(); * } * } * * * @author zhouhao * @see ReactiveAuthenticationSupplier * @since 4.0 */ public final class ReactiveAuthenticationHolder { private static final List suppliers = new CopyOnWriteArrayList<>(); public static final String IGNORE_AUTH_KEY = ".auth.ignore"; static final Context IGNORE_AUTH_CONTEXT_Y = Context.of(IGNORE_AUTH_KEY, true); static final Context IGNORE_AUTH_CONTEXT_N = Context.of(IGNORE_AUTH_KEY, false); private static Mono get(Function> function) { return AuthenticationUtils .merge(Flux.merge(Lists.transform(suppliers, function::apply))); } /** * @return 当前登录的用户权限信息 */ public static Mono get() { return Mono.deferContextual(ctx -> { if (Boolean.TRUE.equals(ctx.getOrDefault(IGNORE_AUTH_KEY, false))) { return Mono.empty(); } return get(ReactiveAuthenticationSupplier::get); }); } /** * 获取指定用户的权限信息 * * @param userId 用户ID * @return 权限信息 */ public static Mono get(String userId) { return get(supplier -> supplier.get(userId)); } /** * 初始化 {@link ReactiveAuthenticationSupplier} * * @param supplier */ public static void addSupplier(ReactiveAuthenticationSupplier supplier) { suppliers.add(supplier); } public static void setSupplier(ReactiveAuthenticationSupplier supplier) { suppliers.clear(); suppliers.add(supplier); } public static Context ignoreContext(boolean ignore) { return ignore ? IGNORE_AUTH_CONTEXT_Y : IGNORE_AUTH_CONTEXT_N; } public static Function ignoreIfAbsent(boolean ignore) { return ctx -> ctx.hasKey(IGNORE_AUTH_KEY) ? ctx : ctx.put(IGNORE_AUTH_KEY, ignore); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationInitializeService.java ================================================ /* * 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.authorization; import org.hswebframework.web.authorization.events.AuthorizationInitializeEvent; import reactor.core.publisher.Mono; /** * 授权信息初始化服务接口,使用该接口初始化用的权限信息 * * @author zhouhao * @since 4.0 */ public interface ReactiveAuthenticationInitializeService { /** * 根据用户ID初始化权限信息 * * @param userId 用户ID * @return 权限信息 * @see AuthorizationInitializeEvent */ Mono initUserAuthorization(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationManager.java ================================================ /* * 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.authorization; import reactor.core.publisher.Mono; /** * 授权信息管理器,用于获取用户授权和同步授权信息 * * @author zhouhao * @see 3.0 */ public interface ReactiveAuthenticationManager { /** * 进行授权操作 * * @param request 授权请求 * @return 授权成功则返回用户权限信息 */ Mono authenticate(Mono request); /** * 根据用户ID获取权限信息 * * @param userId 用户ID * @return 权限信息 */ Mono getByUserId(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationManagerProvider.java ================================================ package org.hswebframework.web.authorization; import reactor.core.publisher.Mono; public interface ReactiveAuthenticationManagerProvider { /** * 进行授权操作 * * @param request 授权请求 * @return 授权成功则返回用户权限信息 */ Mono authenticate(Mono request); /** * 根据用户ID获取权限信息 * * @param userId 用户ID * @return 权限信息 */ Mono getByUserId(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/ReactiveAuthenticationSupplier.java ================================================ /* * 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.authorization; import reactor.core.publisher.Mono; import java.util.function.Supplier; /** * @author zhouhao * @see Supplier * @see Authentication * @see ReactiveAuthenticationHolder * @since 4.0 */ public interface ReactiveAuthenticationSupplier extends Supplier> { Mono get(String userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Role.java ================================================ /* * 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.authorization; import org.hswebframework.web.authorization.simple.SimpleRole; /** * 角色信息 * * @author zhouhao * @since 3.0 */ public interface Role extends Dimension { /** * @return 角色ID */ String getId(); /** * @return 角色名 */ String getName(); @Override default DimensionType getType() { return DefaultDimensionType.role; } static Role fromDimension(Dimension dimension){ return SimpleRole.of(dimension); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/User.java ================================================ /* * 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.authorization; /** * 用户信息 * * @author zhouhao * @since 3.0 */ public interface User extends Dimension { /** * @return 用户ID */ String getId(); /** * @return 用户名 */ String getUsername(); /** * @return 姓名 */ String getName(); /** * @return 用户类型 */ String getUserType(); @Override default DimensionType getType() { return DefaultDimensionType.user; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessConfig.java ================================================ /* * 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.authorization.access; import org.hswebframework.web.authorization.Permission; import java.io.Serializable; /** * 数据级的权限控制,此接口为控制方式配置 * 具体的控制逻辑由控制器{@link DataAccessController}实现 * * @author zhouhao * @see OwnCreatedDataAccessConfig */ public interface DataAccessConfig extends Serializable { /** * 对数据的操作事件 * * @return 操作时间 * @see Permission#ACTION_ADD * @see Permission#ACTION_DELETE * @see Permission#ACTION_GET * @see Permission#ACTION_QUERY * @see Permission#ACTION_UPDATE */ String getAction(); /** * 控制方式标识 * * @return 控制方式 * @see DefaultType */ DataAccessType getType(); /** * 内置的控制方式 */ interface DefaultType { /** * 自己创建的数据 * * @see OwnCreatedDataAccessConfig#getType() */ String OWN_CREATED = "OWN_CREATED"; /** * 禁止操作字段 * * @see FieldFilterDataAccessConfig#getType() */ String DENY_FIELDS = "DENY_FIELDS"; /** * 禁止操作字段 * * @see org.hswebframework.web.authorization.simple.DimensionDataAccessConfig#getType() */ String DIMENSION_SCOPE = "DIMENSION_SCOPE"; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessConfiguration.java ================================================ package org.hswebframework.web.authorization.access; public interface DataAccessConfiguration { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessController.java ================================================ package org.hswebframework.web.authorization.access; import org.hswebframework.web.authorization.define.AuthorizingContext; /** * 数据级别权限控制器,通过此控制器对当前登录用户进行的操作进行数据级别的权限控制。 * 如:A用户只能查询自己创建的B数据,A用户只能修改自己创建的B数据 * * @author zhouhao * @since 3.0 */ @Deprecated public interface DataAccessController { /** * 执行权限控制 * @param access 控制方式以及配置 * @param context 权限验证上下文,用于传递验证过程用到的参数 * @return 授权是否通过 */ boolean doAccess(DataAccessConfig access, AuthorizingContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessHandler.java ================================================ package org.hswebframework.web.authorization.access; import org.hswebframework.web.authorization.define.AuthorizingContext; /** * 数据级别权限控制处理器接口,负责处理支持的权限控制配置 * * @author zhouhao */ public interface DataAccessHandler { /** * 是否支持处理此配置 * * @param access 控制配置 * @return 是否支持 */ boolean isSupport(DataAccessConfig access); /** * 执行处理,返回处理结果 * * @param access 控制配置 * @param context 参数上下文 * @return 处理结果 */ boolean handle(DataAccessConfig access, AuthorizingContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DataAccessType.java ================================================ package org.hswebframework.web.authorization.access; public interface DataAccessType { String getId(); String getName(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DefaultDataAccessType.java ================================================ package org.hswebframework.web.authorization.access; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.web.dict.Dict; import org.hswebframework.web.dict.EnumDict; @Getter @AllArgsConstructor public enum DefaultDataAccessType implements DataAccessType, EnumDict { USER_OWN_DATA("自己的数据"), FIELD_DENY("禁止操作字段"), DIMENSION_SCOPE("维度范围"); private final String name; @Override public String getText() { return name; } @Override public String getValue() { return getId(); } @Override public String getId() { return name().toLowerCase(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/DimensionHelper.java ================================================ package org.hswebframework.web.authorization.access; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.simple.DimensionDataAccessConfig; import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; @NoArgsConstructor(access = AccessLevel.PRIVATE) public abstract class DimensionHelper { public static Set getDimensionDataAccessScope(Authentication atz, Permission permission, String action, String dimensionType) { return permission .getDataAccesses(action) .stream() .filter(DimensionDataAccessConfig.class::isInstance) .map(DimensionDataAccessConfig.class::cast) .filter(conf -> dimensionType.equals(conf.getScopeType())) .flatMap(conf -> { if (CollectionUtils.isEmpty(conf.getScope())) { return atz.getDimensions(dimensionType) .stream() .map(Dimension::getId); } return conf.getScope().stream(); }).collect(Collectors.toSet()); } public static Set getDimensionDataAccessScope(Authentication atz, Permission permission, String action, DimensionType dimensionType) { return getDimensionDataAccessScope(atz, permission, action, dimensionType.getId()); } public static Set getDimensionDataAccessScope(Authentication atz, String permission, String action, String dimensionType) { return atz .getPermission(permission) .map(per -> getDimensionDataAccessScope(atz, per, action, dimensionType)).orElseGet(Collections::emptySet); } public static Set getDimensionDataAccessScope(Authentication atz, String permission, String action, DimensionType dimensionType) { return atz .getPermission(permission) .map(per -> getDimensionDataAccessScope(atz, per, action, dimensionType)) .orElseGet(Collections::emptySet); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/FieldFilterDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.access; import java.util.Set; /** * 对字段进行过滤操作配置,实现字段级别的权限控制 * * @author zhouhao * @see DataAccessConfig * @see org.hswebframework.web.authorization.simple.SimpleFieldFilterDataAccessConfig */ public interface FieldFilterDataAccessConfig extends DataAccessConfig { Set getFields(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/OwnCreatedDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.access; /** * 只能操作由自己创建的数据 * * @author zhouhao */ public interface OwnCreatedDataAccessConfig extends DataAccessConfig { @Override default DataAccessType getType() { return DefaultDataAccessType.USER_OWN_DATA; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/ScopeDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.access; import java.util.Set; /** * 范围数据权限控制配置 * * @author zhouhao * @see DataAccessConfig * @since 3.0 */ public interface ScopeDataAccessConfig extends DataAccessConfig { /** * @return 范围类型 */ String getScopeType(); /** * @return 自定义的控制范围 */ Set getScope(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/access/UserAttachEntity.java ================================================ package org.hswebframework.web.authorization.access; /** * 和user关联的实体 * * @author zhouhao * @since 3.0.6 */ public interface UserAttachEntity { String userId = "userId"; String getUserId(); void setUserId(String userId); default String getUserIdProperty() { return userId; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Authorize.java ================================================ /* * * * 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.authorization.annotation; import org.hswebframework.web.authorization.define.Phased; import java.lang.annotation.*; /** * 基础权限控制注解,提供基本的控制配置 * * @author zhouhao * @see org.hswebframework.web.authorization.Authentication * @see org.hswebframework.web.authorization.define.AuthorizeDefinition * @see Resource * @see ResourceAction * @see Dimension * @see DataAccess * @since 3.0 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Authorize { Resource[] resources() default {}; Dimension[] dimension() default {}; /** * 是否运行匿名访问,匿名访问时,直接允许执行,否则将进行权限验证. * * @return 是否允许匿名访问 * @since 4.0.19 */ boolean anonymous() default false; /** * 验证失败时返回的消息 * * @return 验证失败提示的消息 */ String message() default "无访问权限"; /** * 是否合并类上的注解 * * @return 是否合并类上的注解 */ boolean merge() default true; /** * 验证模式,在使用多个验证条件时有效 * * @return logical */ Logical logical() default Logical.DEFAULT; /** * @return 验证时机,在方法调用前还是调用后 */ Phased phased() default Phased.before; /** * @return 是否忽略, 忽略后将不进行权限控制 */ boolean ignore() default false; String[] description() default {}; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/CreateAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_ADD, name = "新增") public @interface CreateAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccess.java ================================================ /* * 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.authorization.annotation; import org.hswebframework.web.authorization.access.DataAccessController; import java.lang.annotation.*; /** * 数据级权限控制注解,用于进行需要数据级别权限控制的声明. * * 此注解仅用于声明此方法需要进行数据级权限控制,具体权限控制方式由控制器实{@link DataAccessController}现 * * * @author zhouhao * @see DataAccessController * @see ResourceAction#dataAccess() * @since 3.0 * @deprecated 已弃用, 4.1中移除 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Deprecated public @interface DataAccess { DataAccessType[] type() default {}; /** * @return logical */ Logical logical() default Logical.AND; /** * @return 是否忽略, 忽略后将不进行权限控制 */ boolean ignore() default false; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DataAccessType.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.access.DataAccessConfiguration; import org.hswebframework.web.authorization.access.DataAccessController; import java.lang.annotation.*; @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Deprecated public @interface DataAccessType { String id(); //标识 String name(); //名称 String[] description() default {}; /** * @see DataAccessController */ Class extends DataAccessController> controller() default DataAccessController.class; Class extends DataAccessConfiguration> configuration() default DataAccessConfiguration.class; boolean ignore() default false; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DeleteAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_DELETE, name = "删除") public @interface DeleteAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Dimension.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.DimensionType; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 请使用注解继承方式使用此注解 * * @author zhouhao * @see RequiresRoles * @since 4.0 */ @Target({ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(value = Dimension.List.class) public @interface Dimension { /** * 维度类型标识,如: role,org * * @return 维度类型 * @see org.hswebframework.web.authorization.Dimension#getType() * @see DimensionType#getId() * @see org.hswebframework.web.authorization.Authentication#hasDimension(String, String...) */ String type(); /** * 具体的维度ID,如: 角色ID,组织ID * * @return 维度ID * @see org.hswebframework.web.authorization.Dimension#getId() * @see org.hswebframework.web.authorization.Authentication#hasDimension(String, String...) */ String[] id() default {}; /** * 配置了多个ID时的判断逻辑,默认为任意满足则认为有权限. * * @return Logical */ Logical logical() default Logical.DEFAULT; /** * @return 说明 */ String[] description() default {}; /** * @return 是否忽略 */ boolean ignore() default false; @Target({ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @Inherited @interface List { Dimension[] value() default {}; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/DimensionDataAccess.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.define.Phased; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @DataAccessType(id = "dimension", name = "维度数据权限") @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Authorize @Deprecated public @interface DimensionDataAccess { Mapping[] mapping() default {}; @AliasFor(annotation = Authorize.class) Phased phased() default Phased.before; @AliasFor(annotation = DataAccessType.class) boolean ignore() default false; @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @interface Mapping { String dimensionType(); String property(); int idParamIndex() default -1; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Dimensions.java ================================================ package org.hswebframework.web.authorization.annotation; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 标记多个维度的权限控制相关配置 * * @author zhouhao * @since 5.0.1 */ @Target({ElementType.METHOD, TYPE, ANNOTATION_TYPE, FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Dimensions { /** * 存在多个维度时的判断逻辑,默认任意一个满足则认为有权限 * * @return Logical */ Logical logical() default Logical.DEFAULT; /** * @return 针对当前配置的说明信息 */ String[] description() default {}; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/FieldDataAccess.java ================================================ package org.hswebframework.web.authorization.annotation; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; /** * @deprecated 已弃用 */ @DataAccessType(id = "FIELD_DENY", name = "字段权限") @Retention(RetentionPolicy.RUNTIME) @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Deprecated public @interface FieldDataAccess { @AliasFor(annotation = DataAccessType.class) boolean ignore() default false; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Logical.java ================================================ /* * 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.authorization.annotation; public enum Logical { AND, OR, DEFAULT } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/QueryAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_QUERY, name = "查询") public @interface QueryAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/RequiresRoles.java ================================================ package org.hswebframework.web.authorization.annotation; import org.springframework.core.annotation.AliasFor; import java.lang.annotation.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 注解根据角色维度进行权限控制,具有权限的用户才可访问对应的方法. * * {@code * @RequiresRoles("admin") * public Mono handleRequest(){ * * } * } * * @author zhouhao * @see Dimension * @since 4.0 */ @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Dimension(type = "role") @Repeatable(RequiresRoles.List.class) public @interface RequiresRoles { /** * @return 角色ID */ @AliasFor(annotation = Dimension.class, attribute = "id") String[] value() default {}; /** * 多个角色时的判断逻辑 * @return Logical */ @AliasFor(annotation = Dimension.class, attribute = "logical") Logical logical() default Logical.DEFAULT; @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD}) @Retention(RUNTIME) @Documented @Inherited @Dimension.List() @interface List { RequiresRoles[] value(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/Resource.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.define.Phased; import java.lang.annotation.*; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 接口资源声明注解,声明Controller的资源相关信息,用于进行权限控制。 * * 在Controller进行注解,表示此接口需要有对应的权限{@link Permission#getId()}才能进行访问. * 具体的操作权限控制,需要在方法上注解{@link ResourceAction}. * * * * {@code * @RestController * //声明资源 * @Resource(id = "test", name = "测试功能") * public class TestController implements ReactiveCrudController { * * //声明操作,需要有 test:query 权限才能访问此接口 * @QueryAction * public Mono getUser() { * return Authentication.currentReactive() * .switchIfEmpty(Mono.error(new UnAuthorizedException())) * .map(Authentication::getUser); * } * * } * } * * 如果接口不需要进行权限控制,可注解{@link Authorize#ignore()}来标识此接口不需要权限控制. * 或者通过监听 {@link org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent}来进行自定义处理 * {@code * @EventListener * public void handleAuthEvent(AuthorizingHandleBeforeEvent e) { * //admin用户可以访问全部操作 * if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) { * e.setAllow(true); * } * } * } * * @author zhouhao * @see ResourceAction * @see Authorize * @see org.hswebframework.web.authorization.events.AuthorizingHandleBeforeEvent * @since 4.0 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD,ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(Resource.List.class) public @interface Resource { /** * 资源ID * * @return 资源ID */ String id(); /** * @return 资源名称 */ String name(); /** * @return 资源操作定义 */ ResourceAction[] actions() default {}; /** * @return 多个操作控制逻辑 */ Logical logical() default Logical.DEFAULT; /** * @return 权限控制阶段 */ Phased phased() default Phased.before; /** * @return 资源描述 */ String[] description() default {}; /** * @return 资源分组 */ String[] group() default {}; /** * 如果在方法上设置此属性,表示是否合并类上注解的属性 * * @return 是否合并 */ boolean merge() default true; @Target({ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @Inherited @interface List { Resource[] value(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/ResourceAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import java.lang.annotation.*; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 对资源操作的描述,通常用来进行权限控制. * * 在Controller方法上添加此注解,来声明根据权限操作{@link Permission#getActions()}进行权限控制. * * 可以使用注解继承的方式来统一定义操作: * {@code * @Target(ElementType.METHOD) * @Retention(RetentionPolicy.RUNTIME) * @Inherited * @Documented * @ResourceAction(id = "create", name = "新增") * public @interface CreateAction { * * } * } * * * @see CreateAction * @see DeleteAction * @see SaveAction * @see org.hswebframework.web.authorization.Authentication * @see Permission#getActions() */ @Target({ANNOTATION_TYPE, FIELD, METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @Repeatable(ResourceAction.List.class) public @interface ResourceAction { /** * 操作标识 * * @return 操作标识 * @see Permission#getActions() */ String id(); /** * @return 操作名称 */ String name(); /** * @return 操作说明 */ String[] description() default {}; /** * @return 多个操作时的判断逻辑 */ Logical logical() default Logical.DEFAULT; @Target({ANNOTATION_TYPE, FIELD, METHOD}) @Retention(RUNTIME) @Documented @Inherited @interface List { ResourceAction[] value(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/SaveAction.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.Permission; import java.lang.annotation.*; /** * 继承{@link ResourceAction},提供统一的id定义 * * @author zhouhao * @since 4.0 */ @Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @ResourceAction(id = Permission.ACTION_SAVE, name = "保存") public @interface SaveAction { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/TwoFactor.java ================================================ package org.hswebframework.web.authorization.annotation; import org.hswebframework.web.authorization.twofactor.TwoFactorValidator; import java.lang.annotation.*; /** * 开启2FA双重验证 * * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider * @see org.hswebframework.web.authorization.twofactor.TwoFactorValidator * @since 3.0.4 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface TwoFactor { /** * @return 接口的标识, 用于实现不同的操作, 可能会配置不同的验证规则 */ String value(); /** * @return 验证有效期, 超过有效期后需要重新进行验证 */ long timeout() default 10 * 60 * 1000L; /** * 验证器供应商,如: totp,sms,email,由{@link org.hswebframework.web.authorization.twofactor.TwoFactorValidatorProvider}进行自定义. * * 可通过配置项: hsweb.authorize.two-factor.default-provider 来修改默认配置 * * @return provider * @see TwoFactorValidator#getProvider() */ String provider() default "default"; /** * 验证码的http参数名,在进行验证的时候需要传入此参数 * * @return 验证码的参数名 */ String parameter() default "verifyCode"; /** * @return 关闭验证 */ boolean ignore() default false; /** * * @return 错误提示 * @since 3.0.6 */ String message() default "validation.verify_code_error"; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/UserOwnData.java ================================================ package org.hswebframework.web.authorization.annotation; import java.lang.annotation.*; /** * 声明某个操作支持用户查看自己的数据 * * @deprecated 已弃用 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @DataAccessType(id = "user_own_data", name = "用户自己的数据") @Deprecated public @interface UserOwnData { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/AuthenticationBuilder.java ================================================ /* * 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.authorization.builder; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.Role; import org.hswebframework.web.authorization.User; import java.io.Serializable; import java.util.List; import java.util.Map; public interface AuthenticationBuilder extends Serializable { AuthenticationBuilder user(User user); AuthenticationBuilder user(String user); AuthenticationBuilder user(Map user); AuthenticationBuilder role(List role); AuthenticationBuilder role(String role); AuthenticationBuilder permission(List permission); AuthenticationBuilder permission(String permission); AuthenticationBuilder attributes(String attributes); AuthenticationBuilder attributes(Map permission); AuthenticationBuilder json(String json); Authentication build(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/AuthenticationBuilderFactory.java ================================================ package org.hswebframework.web.authorization.builder; /** * 权限构造器工厂 * * @author zhouhao */ public interface AuthenticationBuilderFactory { /** * @return 新建一个权限构造器 */ AuthenticationBuilder create(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/DataAccessConfigBuilder.java ================================================ package org.hswebframework.web.authorization.builder; import org.hswebframework.web.authorization.access.DataAccessConfig; import java.util.Map; /** * * @author zhouhao */ public interface DataAccessConfigBuilder { DataAccessConfigBuilder fromJson(String json); DataAccessConfigBuilder fromMap(Map json); DataAccessConfig build(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/DataAccessConfigBuilderFactory.java ================================================ package org.hswebframework.web.authorization.builder; /** * 数据权限配置构造器工厂 * * @author zhouhao */ public interface DataAccessConfigBuilderFactory { /** * @return 新建一个数据权限配置构造器工厂 */ DataAccessConfigBuilder create(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/context/AuthenticationThreadLocalAccessor.java ================================================ package org.hswebframework.web.authorization.context; import io.micrometer.context.ThreadLocalAccessor; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationHolder; import org.hswebframework.web.authorization.ReactiveAuthenticationHolder; import javax.annotation.Nonnull; public class AuthenticationThreadLocalAccessor implements ThreadLocalAccessor { static final Object KEY = Authentication.class; static { ReactiveAuthenticationHolder.addSupplier( new ThreadLocalReactiveAuthenticationSupplier() ); } @Override @Nonnull public Object key() { return KEY; } @Override public Authentication getValue() { return AuthenticationHolder.get().orElse(null); } @Override public void setValue() { AuthenticationHolder.resetCurrent(); } @Override public void setValue(@Nonnull Authentication value) { AuthenticationHolder.makeCurrent(value); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/context/ThreadLocalReactiveAuthenticationSupplier.java ================================================ package org.hswebframework.web.authorization.context; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationHolder; import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier; import reactor.core.publisher.Mono; class ThreadLocalReactiveAuthenticationSupplier implements ReactiveAuthenticationSupplier { @Override public Mono get(String userId) { return Mono.empty(); } @Override public Mono get() { return Mono.justOrEmpty(AuthenticationHolder.get()); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AopAuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.lang.reflect.Method; /** * @author zhouhao * @since 1.0 */ public interface AopAuthorizeDefinition extends AuthorizeDefinition { Class> getTargetClass(); Method getTargetMethod(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.util.StringJoiner; /** * 权限控制定义,定义权限控制的方式 * * @author zhouhao * @since 3.0 */ public interface AuthorizeDefinition { ResourcesDefinition getResources(); DimensionsDefinition getDimensions(); String getMessage(); Phased getPhased(); boolean isEmpty(); default boolean allowAnonymous() { return false; } default String getDescription() { ResourcesDefinition res = getResources(); StringJoiner joiner = new StringJoiner(";"); for (ResourceDefinition resource : res.getResources()) { joiner.add(resource.getId() + ":" + String.join(",", resource.getActionIds())); } return joiner.toString(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionContext.java ================================================ package org.hswebframework.web.authorization.define; public interface AuthorizeDefinitionContext { void addResource(ResourceDefinition def); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionCustomizer.java ================================================ package org.hswebframework.web.authorization.define; public interface AuthorizeDefinitionCustomizer { void custom(AuthorizeDefinitionContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionInitializedEvent.java ================================================ package org.hswebframework.web.authorization.define; import org.hswebframework.web.authorization.events.AuthorizationEvent; import org.springframework.context.ApplicationEvent; import java.util.List; public class AuthorizeDefinitionInitializedEvent extends ApplicationEvent implements AuthorizationEvent { private static final long serialVersionUID = -8185138454949381441L; public AuthorizeDefinitionInitializedEvent(List all) { super(all); } @SuppressWarnings("unchecked") public List getAllDefinition() { return ((List) getSource()); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizingContext.java ================================================ package org.hswebframework.web.authorization.define; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.aop.MethodInterceptorContext; import org.hswebframework.web.authorization.Authentication; /** * 权限控制上下文 */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class AuthorizingContext { private AuthorizeDefinition definition; private Authentication authentication; private MethodInterceptorContext paramContext; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/CompositeAuthorizeDefinitionCustomizer.java ================================================ package org.hswebframework.web.authorization.define; import lombok.AllArgsConstructor; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @AllArgsConstructor public class CompositeAuthorizeDefinitionCustomizer implements AuthorizeDefinitionCustomizer{ private final List customizers; public CompositeAuthorizeDefinitionCustomizer(Iterable customizers){ this(StreamSupport.stream(customizers.spliterator(),false).collect(Collectors.toList())); } @Override public void custom(AuthorizeDefinitionContext context) { for (AuthorizeDefinitionCustomizer customizer : customizers) { customizer.custom(context); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DataAccessDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.Getter; import lombok.Setter; import java.util.*; @Getter @Setter public class DataAccessDefinition { Set dataAccessTypes = new HashSet<>(); public Optional getType(String typeId) { return dataAccessTypes .stream() .filter(type -> type.getId() != null && type.getId().equalsIgnoreCase(typeId)) .findAny(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DataAccessTypeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.access.DataAccessController; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DataAccessConfiguration; import org.hswebframework.web.bean.FastBeanCopier; @Getter @Setter @EqualsAndHashCode(of = "id") public class DataAccessTypeDefinition implements DataAccessType { private String id; private String name; private String description; private Class extends DataAccessController> controller; private Class extends DataAccessConfiguration> configuration; public DataAccessTypeDefinition copy(){ return FastBeanCopier.copy(this,DataAccessTypeDefinition::new); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DimensionDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.annotation.Logical; import org.hswebframework.web.bean.FastBeanCopier; import reactor.function.Predicate3; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; @Getter @Setter @EqualsAndHashCode(of = "typeId") public class DimensionDefinition { private String typeId; private String typeName; private Set dimensionId = new HashSet<>(); private Logical logical = Logical.DEFAULT; public boolean hasDimension(Predicate3> filter) { return filter.test(typeId,logical, Collections.unmodifiableSet(dimensionId)); } public boolean hasDimension(Set dimensionIdPredicate) { if (logical == Logical.AND) { return dimensionIdPredicate.containsAll(dimensionId); } return dimensionId .stream() .anyMatch(dimensionIdPredicate::contains); } public boolean hasDimension(String id) { return dimensionId.contains(id); } public void addDimensionI(Set id) { dimensionId.addAll(id); } public DimensionDefinition copy() { return FastBeanCopier.copy(this, DimensionDefinition::new); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DimensionsDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Predicate; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.annotation.Logical; import reactor.function.Predicate3; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiPredicate; import java.util.stream.Collectors; @Getter @Setter public class DimensionsDefinition { private Map dimensionsMapping = new ConcurrentHashMap<>(); private Logical logical = Logical.DEFAULT; private String description; public Set getDimensions() { return new HashSet<>(dimensionsMapping.values()); } public void clear() { dimensionsMapping.clear(); } public void addDimension(DimensionDefinition definition) { DimensionDefinition old = dimensionsMapping.putIfAbsent(definition.getTypeId(), definition); if (old != null) { old.addDimensionI(definition.getDimensionId()); } } public boolean isEmpty() { return MapUtils.isEmpty(this.dimensionsMapping); } public boolean hasDimension(Dimension dimension) { DimensionDefinition def = dimensionsMapping.get(dimension.getType().getId()); return def != null && def.hasDimension(dimension.getId()); } public boolean hasDimension(Predicate3> filter) { if (logical == Logical.AND) { return dimensionsMapping .values() .stream() .allMatch(e -> e.hasDimension(filter)); } else { return dimensionsMapping .values() .stream() .anyMatch(e -> e.hasDimension(filter)); } } public boolean hasDimension(List dimensions) { if (logical == Logical.AND) { return dimensions.stream().allMatch(this::hasDimension); } return dimensions.stream().anyMatch(this::hasDimension); } @Override public String toString() { return dimensionsMapping .values() .stream() .map(d -> String.join(",", d.getDimensionId()) + "@" + d.getTypeId()) .collect(Collectors.joining(";")); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/HandleType.java ================================================ package org.hswebframework.web.authorization.define; public enum HandleType{ RBAC,DATA } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/MergedAuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.util.List; import java.util.Set; public class MergedAuthorizeDefinition implements AuthorizeDefinitionContext { private final ResourcesDefinition resources = new ResourcesDefinition(); private final DimensionsDefinition dimensions = new DimensionsDefinition(); public Set getResources() { return resources.getResources(); } public Set getDimensions() { return dimensions.getDimensions(); } public void addResource(ResourceDefinition resource) { resources.addResource(resource, true); } public void addDimension(DimensionDefinition resource) { dimensions.addDimension(resource); } public void merge(List definitions) { for (AuthorizeDefinition definition : definitions) { definition.getResources().getResources().forEach(this::addResource); definition.getDimensions().getDimensions().forEach(this::addDimension); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/Phased.java ================================================ package org.hswebframework.web.authorization.define; public enum Phased { before, after } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourceActionDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.bean.FastBeanCopier; import org.hswebframework.web.i18n.I18nSupportUtils; import org.hswebframework.web.i18n.MultipleI18nSupportEntity; import java.util.Collection; import java.util.HashMap; import java.util.Locale; import java.util.Map; import static org.hswebframework.web.authorization.define.ResourceDefinition.supportLocale; @Getter @Setter @EqualsAndHashCode(of = "id") public class ResourceActionDefinition implements MultipleI18nSupportEntity { private String id; private String name; private String description; private Map> i18nMessages; @Deprecated private DataAccessDefinition dataAccess = new DataAccessDefinition(); private final static String resolveActionPrefix = "hswebframework.web.system.action."; public ResourceActionDefinition copy() { return FastBeanCopier.copy(this, ResourceActionDefinition::new); } public Map> getI18nMessages() { if (org.springframework.util.CollectionUtils.isEmpty(i18nMessages)) { this.i18nMessages = I18nSupportUtils .putI18nMessages( resolveActionPrefix + this.id, "name", supportLocale, null, this.i18nMessages ); } return i18nMessages; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourceDefinition.java ================================================ package org.hswebframework.web.authorization.define; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.annotation.Logical; import org.hswebframework.web.bean.FastBeanCopier; import org.hswebframework.web.i18n.I18nSupportUtils; import org.hswebframework.web.i18n.MultipleI18nSupportEntity; import java.util.*; import java.util.stream.Collectors; @Getter @Setter @EqualsAndHashCode(of = "id") public class ResourceDefinition implements MultipleI18nSupportEntity { private String id; private String name; private String description; private Set actions = new HashSet<>(); private List group; private Map> i18nMessages; @Setter(value = AccessLevel.PRIVATE) @JsonIgnore private volatile Set actionIds; private Logical logical = Logical.DEFAULT; private Phased phased = Phased.before; public final static List supportLocale = new ArrayList<>(); static { supportLocale.add(Locale.CHINESE); supportLocale.add(Locale.ENGLISH); } private final static String resolvePermissionPrefix = "hswebframework.web.system.permission."; public static ResourceDefinition of(String id, String name) { ResourceDefinition definition = new ResourceDefinition(); definition.setId(id); definition.setName(name); return definition; } public Map> getI18nMessages() { if (org.springframework.util.CollectionUtils.isEmpty(i18nMessages)) { this.i18nMessages = I18nSupportUtils .putI18nMessages( resolvePermissionPrefix + this.id, "name", supportLocale, null, this.i18nMessages ); } return i18nMessages; } public ResourceDefinition copy() { ResourceDefinition definition = FastBeanCopier.copy(this, ResourceDefinition::new); definition.setActions(actions.stream().map(ResourceActionDefinition::copy).collect(Collectors.toSet())); return definition; } public ResourceDefinition addAction(String id, String name) { ResourceActionDefinition action = new ResourceActionDefinition(); action.setId(id); action.setName(name); return addAction(action); } public synchronized ResourceDefinition addAction(ResourceActionDefinition action) { actionIds = null; actions.add(action); return this; } public Optional getAction(String action) { return actions.stream() .filter(act -> act.getId().equalsIgnoreCase(action)) .findAny(); } public Set getActionIds() { if (actionIds == null) { actionIds = this.actions .stream() .map(ResourceActionDefinition::getId) .collect(Collectors.toSet()); } return actionIds; } @JsonIgnore public List getDataAccessAction() { return actions.stream() .filter(act -> CollectionUtils.isNotEmpty(act.getDataAccess().getDataAccessTypes())) .collect(Collectors.toList()); } public boolean hasDataAccessAction() { return actions.stream() .anyMatch(act -> CollectionUtils.isNotEmpty(act.getDataAccess().getDataAccessTypes())); } public boolean hasAction(Collection actions) { if (CollectionUtils.isEmpty(this.actions)) { return true; } if (CollectionUtils.isEmpty(actions)) { return false; } if (logical == Logical.AND) { return getActionIds().containsAll(actions); } return getActionIds().stream().anyMatch(actions::contains); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourcesDefinition.java ================================================ package org.hswebframework.web.authorization.define; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.annotation.Logical; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @Getter @Setter public class ResourcesDefinition { private final Set resources = ConcurrentHashMap.newKeySet(); private Logical logical = Logical.DEFAULT; private Phased phased = Phased.before; public void clear() { resources.clear(); } public void addResource(ResourceDefinition resource, boolean merge) { ResourceDefinition definition = getResource(resource.getId()).orElse(null); if (definition != null) { if (merge) { resource.getActions() .stream() .map(ResourceActionDefinition::copy) .forEach(definition::addAction); } else { resources.remove(definition); } } resources.add(resource.copy()); } public Optional getResource(String id) { return resources .stream() .filter(resource -> resource.getId().equals(id)) .findAny(); } @JsonIgnore public List getDataAccessResources() { return resources .stream() .filter(ResourceDefinition::hasDataAccessAction) .collect(Collectors.toList()); } public boolean hasPermission(Permission permission) { if (CollectionUtils.isEmpty(resources)) { return true; } return getResource(permission.getId()) .filter(resource -> resource.hasAction(permission.getActions())) .isPresent(); } public boolean isEmpty() { return resources.isEmpty(); } public boolean hasPermission(Authentication authentication) { int size = resources.size(); if (size == 0) { return true; } if (size == 1) { for (ResourceDefinition resource : resources) { if (authentication.hasPermission(resource.getId(), resource.getActionIds())) { return true; } } return false; } if (logical == Logical.AND) { return resources .stream() .allMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds())); } return resources .stream() .anyMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds())); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionManager.java ================================================ package org.hswebframework.web.authorization.dimension; import reactor.core.publisher.Flux; import java.util.Collection; /** * 维度管理器 * * @author zhouhao * @since 4.0.12 */ public interface DimensionManager { /** * 获取用户维度 * * @param userId 用户ID * @return 用户维度信息 */ Flux getUserDimension(Collection userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserBind.java ================================================ package org.hswebframework.web.authorization.dimension; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class DimensionUserBind implements Externalizable { private static final long serialVersionUID = -6849794470754667710L; private String userId; private String dimensionType; private String dimensionId; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(userId); out.writeUTF(dimensionType); out.writeUTF(dimensionId); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { userId = in.readUTF(); dimensionType = in.readUTF(); dimensionId = in.readUTF(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserBindProvider.java ================================================ package org.hswebframework.web.authorization.dimension; import reactor.core.publisher.Flux; import java.util.Collection; public interface DimensionUserBindProvider { Flux getDimensionBindInfo(Collection userIdList); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserDetail.java ================================================ package org.hswebframework.web.authorization.dimension; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.authorization.Dimension; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class DimensionUserDetail implements Serializable { private static final long serialVersionUID = -6849794470754667710L; private String userId; private List dimensions; public DimensionUserDetail merge(DimensionUserDetail detail) { DimensionUserDetail newDetail = new DimensionUserDetail(); newDetail.setUserId(userId); newDetail.setDimensions(new ArrayList<>()); if (null != dimensions) { newDetail.dimensions.addAll(dimensions); } if (null != detail.getDimensions()) { newDetail.dimensions.addAll(detail.getDimensions()); } return newDetail; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AbstractAuthorizationEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.event.DefaultAsyncEvent; import java.util.Optional; import java.util.function.Function; /** * 抽象授权事件,保存事件常用的数据 * * @author zhouhao * @since 3.0 */ public abstract class AbstractAuthorizationEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -3027505108916079214L; protected String username; protected String password; private final transient Function parameterGetter; /** * 所有参数不能为null * * @param username 用户名 * @param password 密码 * @param parameterGetter 参数获取函数,用户获取授权时传入的参数 */ public AbstractAuthorizationEvent(String username, String password, Function parameterGetter) { if (username == null || password == null || parameterGetter == null) { throw new NullPointerException(); } this.username = username; this.password = password; this.parameterGetter = parameterGetter; } @SuppressWarnings("unchecked") public Optional getParameter(String name) { return Optional.ofNullable((T) parameterGetter.apply(name)); } public String getUsername() { return username; } public String getPassword() { return password; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationBeforeEvent.java ================================================ /* * 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.authorization.events; import lombok.Getter; import org.hswebframework.web.authorization.Authentication; import java.util.function.Function; /** * 授权前事件 * * @author zhouhao * @since 3.0 */ @Getter public class AuthorizationBeforeEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = 5948747533500518524L; private String userId; private Authentication authentication; public AuthorizationBeforeEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public void setAuthorized(String userId) { this.userId = userId; } public void setAuthorized(Authentication authentication) { this.authentication = authentication; } public boolean isAuthorized() { return userId != null || authentication != null; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationDecodeEvent.java ================================================ /* * 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.authorization.events; import java.util.function.Function; /** * 在进行授权时的最开始,触发此事件进行用户名密码解码,解码后请调用{@link #setUsername(String)} {@link #setPassword(String)}重新设置用户名密码 * * @author zhouhao * @since 3.0 */ public class AuthorizationDecodeEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = 5418501934490174251L; public AuthorizationDecodeEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public void setUsername(String username) { super.username = username; } public void setPassword(String password) { super.password = password; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationEvent.java ================================================ /* * 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.authorization.events; /** * 授权事件 * * @author zhouhao * @see AuthorizationSuccessEvent * @see AuthorizationFailedEvent * @see AuthorizationBeforeEvent * @see AuthorizationDecodeEvent * @see AuthorizationExitEvent * @see org.springframework.context.ApplicationEvent * @since 3.0 */ public interface AuthorizationEvent { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationExitEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; import org.springframework.context.ApplicationEvent; /** * 退出登录事件 * * @author zhouhao */ public class AuthorizationExitEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -4590245933665047280L; private final Authentication authentication; public AuthorizationExitEvent(Authentication authentication) { this.authentication = authentication; } public Authentication getAuthentication() { return authentication; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationFailedEvent.java ================================================ /* * 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.authorization.events; import java.util.function.Function; /** * 授权失败时触发 * * @author zhouhao */ public class AuthorizationFailedEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = -101792832265740828L; /** * 异常信息 */ private Throwable exception; public AuthorizationFailedEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public Throwable getException() { return exception; } public void setException(Throwable exception) { this.exception = exception; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationInitializeEvent.java ================================================ package org.hswebframework.web.authorization.events; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; @Getter @Setter @AllArgsConstructor public class AuthorizationInitializeEvent extends DefaultAsyncEvent { private Authentication authentication; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationSuccessEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; /** * 授权成功事件,当授权成功时,触发此事件,并传入授权的信息 * * @author zhouhao * @see Authentication * @since 3.0 */ public class AuthorizationSuccessEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -2452116314154155058L; private final Authentication authentication; private final transient Function parameterGetter; private Map result = new HashMap<>(); public AuthorizationSuccessEvent(Authentication authentication, Function parameterGetter) { this.authentication = authentication; this.parameterGetter = parameterGetter; } public Authentication getAuthentication() { return authentication; } @SuppressWarnings("unchecked") public Optional getParameter(String name) { return Optional.ofNullable((T) parameterGetter.apply(name)); } public Map getResult() { return result; } public void setResult(Map result) { this.result = result; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java ================================================ package org.hswebframework.web.authorization.events; import org.hswebframework.web.authorization.define.AuthorizingContext; import org.hswebframework.web.authorization.define.HandleType; import org.hswebframework.web.event.DefaultAsyncEvent; import org.springframework.context.ApplicationEvent; /** * 权限控制事件,在进行权限控制之前会推送此事件,用于自定义权限控制结果: * {@code * @EventListener * public void handleAuthEvent(AuthorizingHandleBeforeEvent e) { * //admin用户可以访问全部操作 * if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) { * e.setAllow(true); * } * } * } * * @author zhouhao * @since 4.0 */ public class AuthorizingHandleBeforeEvent extends DefaultAsyncEvent implements AuthorizationEvent { private boolean allow = false; private boolean execute = true; private String message; private final AuthorizingContext context; /** * @deprecated 数据权限控制已取消,4.1版本后移除 */ @Deprecated private final HandleType handleType; public AuthorizingHandleBeforeEvent(AuthorizingContext context, HandleType handleType) { this.context = context; this.handleType = handleType; } public AuthorizingContext getContext() { return context; } public boolean isExecute() { return execute; } public boolean isAllow() { return allow; } /** * 设置通过当前请求 * * @param allow allow */ public void setAllow(boolean allow) { execute = false; this.allow = allow; } public String getMessage() { return message; } /** * 设置错误提示消息 * * @param message 消息 */ public void setMessage(String message) { this.message = message; } /** * @return 权限控制类型 */ public HandleType getHandleType() { return handleType; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AccessDenyException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; import org.hswebframework.web.exception.I18nSupportException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.Set; /** * 权限验证异常 * * @author zhouhao * @since 3.0 */ @ResponseStatus(HttpStatus.FORBIDDEN) @Getter public class AccessDenyException extends I18nSupportException { private static final long serialVersionUID = -5135300127303801430L; private String code; public AccessDenyException() { this("error.access_denied"); } public AccessDenyException(String message) { super(message); } public AccessDenyException(String permission, Set actions) { super("error.permission_denied", permission, actions); } public AccessDenyException(String message, String code) { this(message, code, null); } public AccessDenyException(String message, Throwable cause) { this(message, "access_denied", cause); } public AccessDenyException(String message, String code, Throwable cause) { super(message, cause, code); this.code = code; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends AccessDenyException { public NoStackTrace() { super(); } public NoStackTrace(String message) { super(message); } public NoStackTrace(String permission, Set actions) { super(permission, actions); } public NoStackTrace(String message, String code) { super(message, code); } public NoStackTrace(String message, Throwable cause) { super(message, cause); } public NoStackTrace(String message, String code, Throwable cause) { super(message, code, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AuthenticationException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; import org.hswebframework.web.exception.I18nSupportException; @Getter public class AuthenticationException extends I18nSupportException { public static String ILLEGAL_PASSWORD = "illegal_password"; public static String USER_DISABLED = "user_disabled"; private final String code; public AuthenticationException(String code) { this(code, "error." + code); } public AuthenticationException(String code, String message) { super(message); this.code = code; } public AuthenticationException(String code, String message, Throwable cause) { super(message, cause); this.code = code; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends AuthenticationException { public NoStackTrace(String code) { super(code); } public NoStackTrace(String code, String message) { super(code, message); } public NoStackTrace(String code, String message, Throwable cause) { super(code, message, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/NeedTwoFactorException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; /** * @author zhouhao * @since 3.0.4 */ @Getter public class NeedTwoFactorException extends RuntimeException { private static final long serialVersionUID = 3655980280834947633L; private String provider; public NeedTwoFactorException(String message, String provider) { super(message); this.provider = provider; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/UnAuthorizedException.java ================================================ /* * * * 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.authorization.exception; import lombok.Getter; import org.hswebframework.web.authorization.token.TokenState; import org.hswebframework.web.exception.I18nSupportException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; /** * 未授权异常 * * @author zhouhao * @since 3.0 */ @Getter @ResponseStatus(HttpStatus.UNAUTHORIZED) public class UnAuthorizedException extends I18nSupportException { private static final long serialVersionUID = 2422918455013900645L; private final TokenState state; public UnAuthorizedException() { this(TokenState.expired); } public UnAuthorizedException(TokenState state) { this(state.getText(), state); } public UnAuthorizedException(String message, TokenState state) { super(message); this.state = state; } public UnAuthorizedException(String message, TokenState state, Throwable cause) { super(message, cause); this.state = state; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends UnAuthorizedException { public NoStackTrace() { super(); } public NoStackTrace(TokenState state) { super(state); } public NoStackTrace(String message, TokenState state) { super(message, state); } public NoStackTrace(String message, TokenState state, Throwable cause) { super(message, state, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingNullValueHolder.java ================================================ package org.hswebframework.web.authorization.setting; import java.util.List; import java.util.Optional; /** * @author zhouhao * @since 1.0.0 */ public class SettingNullValueHolder implements SettingValueHolder { public static final SettingNullValueHolder INSTANCE = new SettingNullValueHolder(); private SettingNullValueHolder() { } @Override public Optional> asList(Class t) { return Optional.empty(); } @Override public Optional as(Class t) { return Optional.empty(); } @Override public Optional asString() { return Optional.empty(); } @Override public Optional asLong() { return Optional.empty(); } @Override public Optional asInt() { return Optional.empty(); } @Override public Optional asDouble() { return Optional.empty(); } @Override public Optional getValue() { return Optional.empty(); } @Override public UserSettingPermission getPermission() { return UserSettingPermission.NONE; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingValueHolder.java ================================================ package org.hswebframework.web.authorization.setting; import java.util.List; import java.util.Optional; public interface SettingValueHolder { SettingValueHolder NULL = SettingNullValueHolder.INSTANCE; Optional> asList(Class t); Optional as(Class t); Optional asString(); Optional asLong(); Optional asInt(); Optional asDouble(); Optional getValue(); UserSettingPermission getPermission(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/StringSourceSettingHolder.java ================================================ package org.hswebframework.web.authorization.setting; import com.alibaba.fastjson.JSON; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.utils.StringUtils; import org.hswebframework.web.dict.EnumDict; import java.util.List; import java.util.Optional; /** * @author zhouhao * @since 3.0.4 */ @AllArgsConstructor @Getter public class StringSourceSettingHolder implements SettingValueHolder { private String value; private UserSettingPermission permission; public static SettingValueHolder of(String value, UserSettingPermission permission) { if (value == null) { return SettingValueHolder.NULL; } return new StringSourceSettingHolder(value, permission); } @Override public Optional> asList(Class t) { return getNativeValue() .map(v -> JSON.parseArray(v, t)); } protected T convert(String value, Class t) { if (t.isEnum()) { if (EnumDict.class.isAssignableFrom(t)) { T val = (T) EnumDict.find((Class) t, value).orElse(null); if (null != val) { return val; } } for (T enumConstant : t.getEnumConstants()) { if (((Enum) enumConstant).name().equalsIgnoreCase(value)) { return enumConstant; } } } return JSON.parseObject(value, t); } @Override @SuppressWarnings("all") public Optional as(Class t) { if (t == String.class) { return (Optional) asString(); } else if (Long.class == t || long.class == t) { return (Optional) asLong(); } else if (Integer.class == t || int.class == t) { return (Optional) asInt(); } else if (Double.class == t || double.class == t) { return (Optional) asDouble(); } return getNativeValue().map(v -> convert(v, t)); } @Override public Optional asString() { return getNativeValue(); } @Override public Optional asLong() { return getNativeValue().map(StringUtils::toLong); } @Override public Optional asInt() { return getNativeValue().map(StringUtils::toInt); } @Override public Optional asDouble() { return getNativeValue().map(StringUtils::toDouble); } private Optional getNativeValue() { return Optional.ofNullable(value); } @Override public Optional getValue() { return Optional.ofNullable(value); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingManager.java ================================================ package org.hswebframework.web.authorization.setting; /** * @author zhouhao * @since 3.0.4 */ public interface UserSettingManager { SettingValueHolder getSetting(String userId, String key); void saveSetting(String userId, String key, String value, UserSettingPermission permission); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingPermission.java ================================================ package org.hswebframework.web.authorization.setting; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.web.dict.Dict; import org.hswebframework.web.dict.EnumDict; /** * @author zhouhao * @since 3.0.4 */ @AllArgsConstructor @Getter @Dict("user-setting-permission") public enum UserSettingPermission implements EnumDict { NONE("无"), R("读"), W("写"), RW("读写"); private String text; @Override public String getValue() { return name(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/AbstractDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.DataAccessConfig; /** * @author zhouhao * @see DataAccessConfig * @since 3.0 */ public abstract class AbstractDataAccessConfig implements DataAccessConfig { private static final long serialVersionUID = -9025349704771557106L; private String action; @Override public String getAction() { return action; } public void setAction(String action) { this.action = action; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/CompositeReactiveAuthenticationManager.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hswebframework.web.authorization.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; @AllArgsConstructor @Slf4j public class CompositeReactiveAuthenticationManager implements ReactiveAuthenticationManager { private final List providers; @Override public Mono authenticate(Mono request) { return Flux .concat( providers .stream() .map(manager -> manager .authenticate(request) .onErrorResume((err) -> { log.warn("get user authenticate error", err); return Mono.empty(); })) .collect(Collectors.toList())) .take(1) .next(); } @Override public Mono getByUserId(String userId) { if (providers.size() == 1) { return providers.get(0).getByUserId(userId); } return Flux .fromStream(providers .stream() .map(manager -> manager .getByUserId(userId) .onErrorResume((err) -> { log.warn("get user [{}] authentication error", userId, err); return Mono.empty(); }) )) .flatMap(Function.identity()) .as(AuthenticationUtils::merge); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.*; import org.hswebframework.web.authorization.builder.AuthenticationBuilderFactory; import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory; import org.hswebframework.web.authorization.dimension.DimensionManager; import org.hswebframework.web.authorization.dimension.DimensionUserBindProvider; 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.*; import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager; import org.hswebframework.web.authorization.twofactor.defaults.DefaultTwoFactorValidatorManager; import org.hswebframework.web.convert.CustomMessageConverter; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; /** * @author zhouhao */ @AutoConfiguration public class DefaultAuthorizationAutoConfiguration { @Bean @ConditionalOnMissingBean(UserTokenManager.class) @ConfigurationProperties(prefix = "hsweb.user-token") public UserTokenManager userTokenManager() { return new DefaultUserTokenManager(); } @Bean @ConditionalOnMissingBean // @ConditionalOnBean(ReactiveAuthenticationManagerProvider.class) public ReactiveAuthenticationManager reactiveAuthenticationManager(List providers) { return new CompositeReactiveAuthenticationManager(providers); } @Bean @ConditionalOnBean(ReactiveAuthenticationManager.class) public UserTokenReactiveAuthenticationSupplier userTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager, ReactiveAuthenticationManager authenticationManager) { UserTokenReactiveAuthenticationSupplier supplier = new UserTokenReactiveAuthenticationSupplier(userTokenManager, authenticationManager); ReactiveAuthenticationHolder.addSupplier(supplier); return supplier; } @Bean @ConditionalOnBean(AuthenticationManager.class) public UserTokenAuthenticationSupplier userTokenAuthenticationSupplier(UserTokenManager userTokenManager, AuthenticationManager authenticationManager) { UserTokenAuthenticationSupplier supplier = new UserTokenAuthenticationSupplier(userTokenManager, authenticationManager); AuthenticationHolder.addSupplier(supplier); return supplier; } @Bean @ConditionalOnMissingBean(DataAccessConfigBuilderFactory.class) @ConfigurationProperties(prefix = "hsweb.authorization.data-access", ignoreInvalidFields = true) public SimpleDataAccessConfigBuilderFactory dataAccessConfigBuilderFactory() { return new SimpleDataAccessConfigBuilderFactory(); } @Bean @ConditionalOnMissingBean(AuthenticationBuilderFactory.class) public AuthenticationBuilderFactory authenticationBuilderFactory(DataAccessConfigBuilderFactory dataAccessConfigBuilderFactory) { return new SimpleAuthenticationBuilderFactory(dataAccessConfigBuilderFactory); } @Bean public CustomMessageConverter authenticationCustomMessageConverter(AuthenticationBuilderFactory factory) { return new CustomMessageConverter() { @Override public boolean support(Class clazz) { return clazz == Authentication.class; } @Override public Object convert(Class clazz, byte[] message) { String json = new String(message); return factory.create().json(json).build(); } }; } @Bean @ConditionalOnMissingBean(DimensionManager.class) public DimensionManager defaultDimensionManager(ObjectProviderbindProviders, ObjectProvider providers){ DefaultDimensionManager manager = new DefaultDimensionManager(); bindProviders.forEach(manager::addBindProvider); providers.forEach(manager::addProvider); return manager; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultDimensionManager.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionProvider; import org.hswebframework.web.authorization.dimension.DimensionManager; import org.hswebframework.web.authorization.dimension.DimensionUserBind; import org.hswebframework.web.authorization.dimension.DimensionUserBindProvider; import org.hswebframework.web.authorization.dimension.DimensionUserDetail; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; import java.util.stream.Collectors; public class DefaultDimensionManager implements DimensionManager { private final List dimensionProviders = new CopyOnWriteArrayList<>(); private final List bindProviders = new CopyOnWriteArrayList<>(); private final Mono> providerMapping = Flux .defer(() -> Flux.fromIterable(dimensionProviders)) .flatMap(provider -> provider .getAllType() .map(type -> Tuples.of(type.getId(), provider))) .collectMap(Tuple2::getT1, Tuple2::getT2); public DefaultDimensionManager() { } public void addProvider(DimensionProvider provider) { dimensionProviders.add(provider); } public void addBindProvider(DimensionUserBindProvider bindProvider) { bindProviders.add(bindProvider); } private Mono> providerMapping() { return providerMapping; } @Override public Flux getUserDimension(Collection userId) { return this .providerMapping() .flatMapMany(providerMapping -> Flux .fromIterable(bindProviders) //获取绑定信息 .flatMap(provider -> provider.getDimensionBindInfo(userId)) .groupBy(DimensionUserBind::getDimensionType) .flatMap(group -> { String type = group.key(); Flux binds = group.cache(); DimensionProvider provider = providerMapping.get(type); if (null == provider) { return Mono.empty(); } //获取维度信息 return binds .map(DimensionUserBind::getDimensionId) .collect(Collectors.toSet()) .flatMapMany(idList -> provider.getDimensionsById(SimpleDimensionType.of(type), idList)) .collectMap(Dimension::getId, Function.identity()) .flatMapMany(mapping -> binds .groupBy(DimensionUserBind::getUserId) .flatMap(userGroup -> Mono .zip( Mono.just(userGroup.key()), userGroup .handle((bind, sink) -> { Dimension dimension = mapping.get(bind.getDimensionId()); if (dimension != null) { sink.next(dimension); } }) .collectList(), DimensionUserDetail::of )) ); }) ) .groupBy(DimensionUserDetail::getUserId) .flatMap(group->group.reduce(DimensionUserDetail::merge)); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DimensionDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DefaultDataAccessType; import org.hswebframework.web.authorization.access.ScopeDataAccessConfig; import org.hswebframework.web.authorization.simple.AbstractDataAccessConfig; import java.util.Set; @Getter @Setter @EqualsAndHashCode(callSuper = true) public class DimensionDataAccessConfig extends AbstractDataAccessConfig implements ScopeDataAccessConfig { private Set scope; private boolean children; /** * @see DimensionType#getId() */ private String scopeType; @Override public DefaultDataAccessType getType() { return DefaultDataAccessType.DIMENSION_SCOPE; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/PlainTextUsernamePasswordAuthenticationRequest.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.authorization.AuthenticationRequest; /** * @author zhouhao * @since 3.0.0-RC */ @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class PlainTextUsernamePasswordAuthenticationRequest implements AuthenticationRequest { private String username; private String password; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java ================================================ /* * 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.authorization.simple; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.*; import java.io.Serial; import java.io.Serializable; import java.util.*; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; public class SimpleAuthentication implements Authentication { static final AtomicLongFieldUpdater ACCESS_COUNT_UPDATER = AtomicLongFieldUpdater.newUpdater(SimpleAuthentication.class, "accessCount"); @Serial private static final long serialVersionUID = -2898863220255336528L; @Getter private User user; @Setter private List permissions = new ArrayList<>(); private List dimensions = new ArrayList<>(); @Setter private Map attributes = new HashMap<>(); public static Authentication of() { return new SimpleAuthentication(); } @Override @SuppressWarnings("unchecked") public Optional getAttribute(String name) { return Optional.ofNullable((T) attributes.get(name)); } public List getDimensions() { return dimensions == null ? Collections.emptyList() : dimensions; } public List getPermissions() { return permissions == null ? Collections.emptyList() : permissions; } @Override public Map getAttributes() { return attributes == null ? Collections.emptyMap() : attributes; } public SimpleAuthentication merge(Authentication authentication) { Map mePermissionGroup = permissions .stream() .collect(Collectors.toMap(Permission::getId, Function.identity())); if (authentication.getUser() != null) { user = authentication.getUser(); } this.attributes = new HashMap<>(getAttributes()); this.attributes.putAll(authentication.getAttributes()); this.permissions = new ArrayList<>(this.getPermissions()); for (Permission permission : authentication.getPermissions()) { Permission me = mePermissionGroup.get(permission.getId()); if (me == null) { permissions.add(permission.copy()); continue; } me.getActions().addAll(permission.getActions()); } this.dimensions = new ArrayList<>(this.getDimensions()); for (Dimension dimension : authentication.getDimensions()) { if (getDimension(dimension.getType(), dimension.getId()).isEmpty()) { dimensions.add(dimension); } } return this; } protected SimpleAuthentication newInstance() { return new SimpleAuthentication(); } @Override public Authentication copy(BiPredicate permissionFilter, Predicate dimension) { SimpleAuthentication authentication = newInstance(); 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()) ); if (user != null) { authentication.setUser0(user); } authentication.setAttributes(new HashMap<>(attributes)); return authentication; } public void setUser(User user) { this.user = user; dimensions.add(user); } protected void setUser0(User user) { this.user = user; } public void setDimensions(List dimensions) { this.dimensions.addAll(dimensions); } public void setDimensions(Collection dimensions) { this.dimensions.addAll(dimensions); } public void addDimension(Dimension dimension) { this.dimensions.add(dimension); } private transient volatile Map> dimensionMapping; private transient volatile Map permissionMapping; private transient volatile long accessCount; protected boolean fastPath() { // 总共访问超过8次,则进行初始化缓存. if (ACCESS_COUNT_UPDATER.incrementAndGet(this) == 8) { if (permissionMapping == null) { permissionMapping = permissions == null ? Collections.emptyMap() : permissions .stream() .collect(Collectors .toMap(Permission::getId, Function.identity(), (a, b) -> b)); dimensionMapping = dimensions == null ? Collections.emptyMap() : dimensions .stream() .collect(Collectors .groupingBy(d -> d.getType().getId(), Collectors.toMap( Dimension::getId, Function.identity(), (a, b) -> a))); } } return permissionMapping != null; } @Override public boolean hasPermission(String permissionId, Collection actions) { Map permissionMapping = this.permissionMapping; if (fastPath() && permissionMapping != null) { Permission permission = permissionMapping.get(permissionId); if (permission == null) { permission = permissionMapping.get("*"); } if (permission == null) { return false; } return actions.isEmpty() || permission.getActions().containsAll(actions) || permission.getActions().contains("*"); } return Authentication.super.hasPermission(permissionId, actions); } @Override public Optional getDimension(String type, String id) { Map> dimensionMapping = this.dimensionMapping; if (fastPath() && dimensionMapping != null) { Map mapping = dimensionMapping.get(type); if (mapping == null) { return Optional.empty(); } return Optional.ofNullable(mapping.get(id)); } return Authentication.super.getDimension(type, id); } @Override public Optional getDimension(DimensionType type, String id) { return getDimension(type.getId(), id); } @Override public List getDimensions(DimensionType type) { return this.getDimensions(type.getId()); } @Override public List getDimensions(String type) { Map> dimensionMapping = this.dimensionMapping; if (fastPath() && dimensionMapping != null) { Map mapping = dimensionMapping.get(type); if (mapping == null) { return List.of(); } return new ArrayList<>(mapping.values()); } return Authentication.super.getDimensions(type); } @Override public Optional getPermission(String id) { Map permissionMapping = this.permissionMapping; if (fastPath() && permissionMapping != null) { return Optional.ofNullable(permissionMapping.get(id)); } return Authentication.super.getPermission(id); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleDimension.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionType; import java.util.Map; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor @EqualsAndHashCode public class SimpleDimension implements Dimension { private String id; private String name; private DimensionType type; private Map options; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleDimensionType.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.DimensionType; import java.io.Serializable; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor @EqualsAndHashCode public class SimpleDimensionType implements DimensionType, Serializable { private static final long serialVersionUID = -6849794470754667710L; private String id; private String name; public static SimpleDimensionType of(String id) { return of(id, id); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleFieldFilterDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DefaultDataAccessType; import org.hswebframework.web.authorization.access.FieldFilterDataAccessConfig; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import static org.hswebframework.web.authorization.access.DataAccessConfig.DefaultType.DENY_FIELDS; /** * 默认配置实现 * * @author zhouhao * @see FieldFilterDataAccessConfig * @since 3.0 */ public class SimpleFieldFilterDataAccessConfig extends AbstractDataAccessConfig implements FieldFilterDataAccessConfig { private static final long serialVersionUID = 8080660575093151866L; private Set fields; public SimpleFieldFilterDataAccessConfig() { } public SimpleFieldFilterDataAccessConfig(String... fields) { this.fields = new HashSet<>(Arrays.asList(fields)); } @Override public Set getFields() { return fields; } public void setFields(Set fields) { this.fields = fields; } @Override public DataAccessType getType() { return DefaultDataAccessType.FIELD_DENY; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleOwnCreatedDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.OwnCreatedDataAccessConfig; /** * @author zhouhao * @since 3.0 */ public class SimpleOwnCreatedDataAccessConfig extends AbstractDataAccessConfig implements OwnCreatedDataAccessConfig { private static final long serialVersionUID = -6059330812806119730L; public SimpleOwnCreatedDataAccessConfig() { } public SimpleOwnCreatedDataAccessConfig(String action) { setAction(action); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.apache.commons.collections4.CollectionUtils; 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 */ @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(exclude = "dataAccesses") public class SimplePermission implements Permission { private static final long serialVersionUID = 7587266693680162184L; private String id; private String name; private Set actions; private Set dataAccesses; private Map options; public Set getActions() { if (actions == null) { actions = new java.util.HashSet<>(); } return actions; } public Set getDataAccesses() { if (dataAccesses == null) { dataAccesses = new java.util.HashSet<>(); } return dataAccesses; } @Override public Permission copy(Predicate actionFilter, Predicate dataAccessFilter) { SimplePermission permission = new SimplePermission(); permission.setId(id); permission.setName(name); 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); } @Override public String toString() { return id + (CollectionUtils.isNotEmpty(actions) ? ":" + String.join(",", actions) : ""); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleRole.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.Role; import java.io.Serializable; import java.util.Map; /** * @author zhouhao */ @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode public class SimpleRole implements Role { private static final long serialVersionUID = 7460859165231311347L; private String id; private String name; private Map options; public static Role of(Dimension dimension) { return SimpleRole.builder() .name(dimension.getName()) .id(dimension.getId()) .options(dimension.getOptions()) .build(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleUser.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.User; import java.io.Serial; import java.io.Serializable; import java.util.Map; /** * @author zhouhao */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @EqualsAndHashCode public class SimpleUser implements User { @Serial private static final long serialVersionUID = 2194541828191869091L; private String id; private String username; private String name; private String userType; private Map options; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/builder/DataAccessConfigConverter.java ================================================ package org.hswebframework.web.authorization.simple.builder; import org.hswebframework.web.authorization.access.DataAccessConfig; /** * @author zhouhao */ public interface DataAccessConfigConverter { boolean isSupport(String type, String action, String config); DataAccessConfig convert(String type, String action, String config); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/builder/SimpleAuthenticationBuilder.java ================================================ package org.hswebframework.web.authorization.simple.builder; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Maps; import org.hswebframework.web.authorization.*; import org.hswebframework.web.authorization.builder.AuthenticationBuilder; import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory; import org.hswebframework.web.authorization.simple.*; import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; /** * @author zhouhao */ public class SimpleAuthenticationBuilder implements AuthenticationBuilder { private SimpleAuthentication authentication = new SimpleAuthentication(); private DataAccessConfigBuilderFactory dataBuilderFactory; public SimpleAuthenticationBuilder(DataAccessConfigBuilderFactory dataBuilderFactory) { this.dataBuilderFactory = dataBuilderFactory; } public void setDataBuilderFactory(DataAccessConfigBuilderFactory dataBuilderFactory) { this.dataBuilderFactory = dataBuilderFactory; } @Override public AuthenticationBuilder user(User user) { Objects.requireNonNull(user); authentication.setUser(user); return this; } @Override public AuthenticationBuilder user(String user) { return user(JSON.parseObject(user, SimpleUser.class)); } @Override public AuthenticationBuilder user(Map user) { Objects.requireNonNull(user.get("id")); user(SimpleUser.builder() .id(user.get("id")) .username(user.get("username")) .name(user.get("name")) .userType(user.get("type")) .build()); return this; } @Override public AuthenticationBuilder role(List role) { authentication.getDimensions().addAll(role); return this; } @Override @SuppressWarnings("unchecked") public AuthenticationBuilder role(String role) { return role((List) JSON.parseArray(role, SimpleRole.class)); } @Override public AuthenticationBuilder permission(List permission) { authentication.setPermissions(permission); return this; } public AuthenticationBuilder permission(JSONArray jsonArray) { List permissions = new ArrayList<>(); for (int i = 0; i < jsonArray.size(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); SimplePermission permission = new SimplePermission(); permission.setId(jsonObject.getString("id")); permission.setName(jsonObject.getString("name")); permission.setOptions(jsonObject.getJSONObject("options")); JSONArray actions = jsonObject.getJSONArray("actions"); if (actions != null) { permission.setActions(new HashSet<>(actions.toJavaList(String.class))); } JSONArray dataAccess = jsonObject.getJSONArray("dataAccesses"); if (null != dataAccess) { permission.setDataAccesses(dataAccess.stream().map(JSONObject.class::cast) .map(dataJson -> dataBuilderFactory .create() .fromJson(dataJson.toJSONString()) .build()) .filter(Objects::nonNull) .collect(Collectors.toSet())); } permissions.add(permission); } authentication.setPermissions(permissions); return this; } @Override public AuthenticationBuilder permission(String permissionJson) { return permission(JSON.parseArray(permissionJson)); } @Override public AuthenticationBuilder attributes(String attributes) { authentication.getAttributes().putAll(JSON.>parseObject(attributes, Map.class)); return this; } @Override public AuthenticationBuilder attributes(Map permission) { authentication.getAttributes().putAll(permission); return this; } public AuthenticationBuilder dimension(JSONArray json) { if (json == null) { return this; } List dimensions = new ArrayList<>(); for (int i = 0; i < json.size(); i++) { JSONObject jsonObject = json.getJSONObject(i); Object type = jsonObject.get("type"); Map
{@code * @RequestMapping("/example") * public Mono example(){ * return ReactiveAuthenticationHolder.get(); * } * } *
* 此注解仅用于声明此方法需要进行数据级权限控制,具体权限控制方式由控制器实{@link DataAccessController}现 *
{@code * @RequiresRoles("admin") * public Mono handleRequest(){ * * } * }
{@code * @RestController * //声明资源 * @Resource(id = "test", name = "测试功能") * public class TestController implements ReactiveCrudController { * * //声明操作,需要有 test:query 权限才能访问此接口 * @QueryAction * public Mono getUser() { * return Authentication.currentReactive() * .switchIfEmpty(Mono.error(new UnAuthorizedException())) * .map(Authentication::getUser); * } * * } * } *
{@code * @EventListener * public void handleAuthEvent(AuthorizingHandleBeforeEvent e) { * //admin用户可以访问全部操作 * if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) { * e.setAllow(true); * } * } * }
* 在Controller方法上添加此注解,来声明根据权限操作{@link Permission#getActions()}进行权限控制. *
* 可以使用注解继承的方式来统一定义操作: *
{@code * @Target(ElementType.METHOD) * @Retention(RetentionPolicy.RUNTIME) * @Inherited * @Documented * @ResourceAction(id = "create", name = "新增") * public @interface CreateAction { * * } * } *
* 可通过配置项: hsweb.authorize.two-factor.default-provider 来修改默认配置 * * @return provider * @see TwoFactorValidator#getProvider() */ String provider() default "default"; /** * 验证码的http参数名,在进行验证的时候需要传入此参数 * * @return 验证码的参数名 */ String parameter() default "verifyCode"; /** * @return 关闭验证 */ boolean ignore() default false; /** * * @return 错误提示 * @since 3.0.6 */ String message() default "validation.verify_code_error"; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/annotation/UserOwnData.java ================================================ package org.hswebframework.web.authorization.annotation; import java.lang.annotation.*; /** * 声明某个操作支持用户查看自己的数据 * * @deprecated 已弃用 */ @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @DataAccessType(id = "user_own_data", name = "用户自己的数据") @Deprecated public @interface UserOwnData { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/AuthenticationBuilder.java ================================================ /* * 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.authorization.builder; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.Role; import org.hswebframework.web.authorization.User; import java.io.Serializable; import java.util.List; import java.util.Map; public interface AuthenticationBuilder extends Serializable { AuthenticationBuilder user(User user); AuthenticationBuilder user(String user); AuthenticationBuilder user(Map user); AuthenticationBuilder role(List role); AuthenticationBuilder role(String role); AuthenticationBuilder permission(List permission); AuthenticationBuilder permission(String permission); AuthenticationBuilder attributes(String attributes); AuthenticationBuilder attributes(Map permission); AuthenticationBuilder json(String json); Authentication build(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/AuthenticationBuilderFactory.java ================================================ package org.hswebframework.web.authorization.builder; /** * 权限构造器工厂 * * @author zhouhao */ public interface AuthenticationBuilderFactory { /** * @return 新建一个权限构造器 */ AuthenticationBuilder create(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/DataAccessConfigBuilder.java ================================================ package org.hswebframework.web.authorization.builder; import org.hswebframework.web.authorization.access.DataAccessConfig; import java.util.Map; /** * * @author zhouhao */ public interface DataAccessConfigBuilder { DataAccessConfigBuilder fromJson(String json); DataAccessConfigBuilder fromMap(Map json); DataAccessConfig build(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/builder/DataAccessConfigBuilderFactory.java ================================================ package org.hswebframework.web.authorization.builder; /** * 数据权限配置构造器工厂 * * @author zhouhao */ public interface DataAccessConfigBuilderFactory { /** * @return 新建一个数据权限配置构造器工厂 */ DataAccessConfigBuilder create(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/context/AuthenticationThreadLocalAccessor.java ================================================ package org.hswebframework.web.authorization.context; import io.micrometer.context.ThreadLocalAccessor; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationHolder; import org.hswebframework.web.authorization.ReactiveAuthenticationHolder; import javax.annotation.Nonnull; public class AuthenticationThreadLocalAccessor implements ThreadLocalAccessor { static final Object KEY = Authentication.class; static { ReactiveAuthenticationHolder.addSupplier( new ThreadLocalReactiveAuthenticationSupplier() ); } @Override @Nonnull public Object key() { return KEY; } @Override public Authentication getValue() { return AuthenticationHolder.get().orElse(null); } @Override public void setValue() { AuthenticationHolder.resetCurrent(); } @Override public void setValue(@Nonnull Authentication value) { AuthenticationHolder.makeCurrent(value); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/context/ThreadLocalReactiveAuthenticationSupplier.java ================================================ package org.hswebframework.web.authorization.context; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.AuthenticationHolder; import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier; import reactor.core.publisher.Mono; class ThreadLocalReactiveAuthenticationSupplier implements ReactiveAuthenticationSupplier { @Override public Mono get(String userId) { return Mono.empty(); } @Override public Mono get() { return Mono.justOrEmpty(AuthenticationHolder.get()); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AopAuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.lang.reflect.Method; /** * @author zhouhao * @since 1.0 */ public interface AopAuthorizeDefinition extends AuthorizeDefinition { Class> getTargetClass(); Method getTargetMethod(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.util.StringJoiner; /** * 权限控制定义,定义权限控制的方式 * * @author zhouhao * @since 3.0 */ public interface AuthorizeDefinition { ResourcesDefinition getResources(); DimensionsDefinition getDimensions(); String getMessage(); Phased getPhased(); boolean isEmpty(); default boolean allowAnonymous() { return false; } default String getDescription() { ResourcesDefinition res = getResources(); StringJoiner joiner = new StringJoiner(";"); for (ResourceDefinition resource : res.getResources()) { joiner.add(resource.getId() + ":" + String.join(",", resource.getActionIds())); } return joiner.toString(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionContext.java ================================================ package org.hswebframework.web.authorization.define; public interface AuthorizeDefinitionContext { void addResource(ResourceDefinition def); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionCustomizer.java ================================================ package org.hswebframework.web.authorization.define; public interface AuthorizeDefinitionCustomizer { void custom(AuthorizeDefinitionContext context); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizeDefinitionInitializedEvent.java ================================================ package org.hswebframework.web.authorization.define; import org.hswebframework.web.authorization.events.AuthorizationEvent; import org.springframework.context.ApplicationEvent; import java.util.List; public class AuthorizeDefinitionInitializedEvent extends ApplicationEvent implements AuthorizationEvent { private static final long serialVersionUID = -8185138454949381441L; public AuthorizeDefinitionInitializedEvent(List all) { super(all); } @SuppressWarnings("unchecked") public List getAllDefinition() { return ((List) getSource()); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/AuthorizingContext.java ================================================ package org.hswebframework.web.authorization.define; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.aop.MethodInterceptorContext; import org.hswebframework.web.authorization.Authentication; /** * 权限控制上下文 */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class AuthorizingContext { private AuthorizeDefinition definition; private Authentication authentication; private MethodInterceptorContext paramContext; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/CompositeAuthorizeDefinitionCustomizer.java ================================================ package org.hswebframework.web.authorization.define; import lombok.AllArgsConstructor; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @AllArgsConstructor public class CompositeAuthorizeDefinitionCustomizer implements AuthorizeDefinitionCustomizer{ private final List customizers; public CompositeAuthorizeDefinitionCustomizer(Iterable customizers){ this(StreamSupport.stream(customizers.spliterator(),false).collect(Collectors.toList())); } @Override public void custom(AuthorizeDefinitionContext context) { for (AuthorizeDefinitionCustomizer customizer : customizers) { customizer.custom(context); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DataAccessDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.Getter; import lombok.Setter; import java.util.*; @Getter @Setter public class DataAccessDefinition { Set dataAccessTypes = new HashSet<>(); public Optional getType(String typeId) { return dataAccessTypes .stream() .filter(type -> type.getId() != null && type.getId().equalsIgnoreCase(typeId)) .findAny(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DataAccessTypeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.access.DataAccessController; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DataAccessConfiguration; import org.hswebframework.web.bean.FastBeanCopier; @Getter @Setter @EqualsAndHashCode(of = "id") public class DataAccessTypeDefinition implements DataAccessType { private String id; private String name; private String description; private Class extends DataAccessController> controller; private Class extends DataAccessConfiguration> configuration; public DataAccessTypeDefinition copy(){ return FastBeanCopier.copy(this,DataAccessTypeDefinition::new); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DimensionDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.annotation.Logical; import org.hswebframework.web.bean.FastBeanCopier; import reactor.function.Predicate3; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Predicate; @Getter @Setter @EqualsAndHashCode(of = "typeId") public class DimensionDefinition { private String typeId; private String typeName; private Set dimensionId = new HashSet<>(); private Logical logical = Logical.DEFAULT; public boolean hasDimension(Predicate3> filter) { return filter.test(typeId,logical, Collections.unmodifiableSet(dimensionId)); } public boolean hasDimension(Set dimensionIdPredicate) { if (logical == Logical.AND) { return dimensionIdPredicate.containsAll(dimensionId); } return dimensionId .stream() .anyMatch(dimensionIdPredicate::contains); } public boolean hasDimension(String id) { return dimensionId.contains(id); } public void addDimensionI(Set id) { dimensionId.addAll(id); } public DimensionDefinition copy() { return FastBeanCopier.copy(this, DimensionDefinition::new); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/DimensionsDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.collections4.Predicate; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.annotation.Logical; import reactor.function.Predicate3; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiPredicate; import java.util.stream.Collectors; @Getter @Setter public class DimensionsDefinition { private Map dimensionsMapping = new ConcurrentHashMap<>(); private Logical logical = Logical.DEFAULT; private String description; public Set getDimensions() { return new HashSet<>(dimensionsMapping.values()); } public void clear() { dimensionsMapping.clear(); } public void addDimension(DimensionDefinition definition) { DimensionDefinition old = dimensionsMapping.putIfAbsent(definition.getTypeId(), definition); if (old != null) { old.addDimensionI(definition.getDimensionId()); } } public boolean isEmpty() { return MapUtils.isEmpty(this.dimensionsMapping); } public boolean hasDimension(Dimension dimension) { DimensionDefinition def = dimensionsMapping.get(dimension.getType().getId()); return def != null && def.hasDimension(dimension.getId()); } public boolean hasDimension(Predicate3> filter) { if (logical == Logical.AND) { return dimensionsMapping .values() .stream() .allMatch(e -> e.hasDimension(filter)); } else { return dimensionsMapping .values() .stream() .anyMatch(e -> e.hasDimension(filter)); } } public boolean hasDimension(List dimensions) { if (logical == Logical.AND) { return dimensions.stream().allMatch(this::hasDimension); } return dimensions.stream().anyMatch(this::hasDimension); } @Override public String toString() { return dimensionsMapping .values() .stream() .map(d -> String.join(",", d.getDimensionId()) + "@" + d.getTypeId()) .collect(Collectors.joining(";")); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/HandleType.java ================================================ package org.hswebframework.web.authorization.define; public enum HandleType{ RBAC,DATA } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/MergedAuthorizeDefinition.java ================================================ package org.hswebframework.web.authorization.define; import java.util.List; import java.util.Set; public class MergedAuthorizeDefinition implements AuthorizeDefinitionContext { private final ResourcesDefinition resources = new ResourcesDefinition(); private final DimensionsDefinition dimensions = new DimensionsDefinition(); public Set getResources() { return resources.getResources(); } public Set getDimensions() { return dimensions.getDimensions(); } public void addResource(ResourceDefinition resource) { resources.addResource(resource, true); } public void addDimension(DimensionDefinition resource) { dimensions.addDimension(resource); } public void merge(List definitions) { for (AuthorizeDefinition definition : definitions) { definition.getResources().getResources().forEach(this::addResource); definition.getDimensions().getDimensions().forEach(this::addDimension); } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/Phased.java ================================================ package org.hswebframework.web.authorization.define; public enum Phased { before, after } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourceActionDefinition.java ================================================ package org.hswebframework.web.authorization.define; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.bean.FastBeanCopier; import org.hswebframework.web.i18n.I18nSupportUtils; import org.hswebframework.web.i18n.MultipleI18nSupportEntity; import java.util.Collection; import java.util.HashMap; import java.util.Locale; import java.util.Map; import static org.hswebframework.web.authorization.define.ResourceDefinition.supportLocale; @Getter @Setter @EqualsAndHashCode(of = "id") public class ResourceActionDefinition implements MultipleI18nSupportEntity { private String id; private String name; private String description; private Map> i18nMessages; @Deprecated private DataAccessDefinition dataAccess = new DataAccessDefinition(); private final static String resolveActionPrefix = "hswebframework.web.system.action."; public ResourceActionDefinition copy() { return FastBeanCopier.copy(this, ResourceActionDefinition::new); } public Map> getI18nMessages() { if (org.springframework.util.CollectionUtils.isEmpty(i18nMessages)) { this.i18nMessages = I18nSupportUtils .putI18nMessages( resolveActionPrefix + this.id, "name", supportLocale, null, this.i18nMessages ); } return i18nMessages; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourceDefinition.java ================================================ package org.hswebframework.web.authorization.define; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.annotation.Logical; import org.hswebframework.web.bean.FastBeanCopier; import org.hswebframework.web.i18n.I18nSupportUtils; import org.hswebframework.web.i18n.MultipleI18nSupportEntity; import java.util.*; import java.util.stream.Collectors; @Getter @Setter @EqualsAndHashCode(of = "id") public class ResourceDefinition implements MultipleI18nSupportEntity { private String id; private String name; private String description; private Set actions = new HashSet<>(); private List group; private Map> i18nMessages; @Setter(value = AccessLevel.PRIVATE) @JsonIgnore private volatile Set actionIds; private Logical logical = Logical.DEFAULT; private Phased phased = Phased.before; public final static List supportLocale = new ArrayList<>(); static { supportLocale.add(Locale.CHINESE); supportLocale.add(Locale.ENGLISH); } private final static String resolvePermissionPrefix = "hswebframework.web.system.permission."; public static ResourceDefinition of(String id, String name) { ResourceDefinition definition = new ResourceDefinition(); definition.setId(id); definition.setName(name); return definition; } public Map> getI18nMessages() { if (org.springframework.util.CollectionUtils.isEmpty(i18nMessages)) { this.i18nMessages = I18nSupportUtils .putI18nMessages( resolvePermissionPrefix + this.id, "name", supportLocale, null, this.i18nMessages ); } return i18nMessages; } public ResourceDefinition copy() { ResourceDefinition definition = FastBeanCopier.copy(this, ResourceDefinition::new); definition.setActions(actions.stream().map(ResourceActionDefinition::copy).collect(Collectors.toSet())); return definition; } public ResourceDefinition addAction(String id, String name) { ResourceActionDefinition action = new ResourceActionDefinition(); action.setId(id); action.setName(name); return addAction(action); } public synchronized ResourceDefinition addAction(ResourceActionDefinition action) { actionIds = null; actions.add(action); return this; } public Optional getAction(String action) { return actions.stream() .filter(act -> act.getId().equalsIgnoreCase(action)) .findAny(); } public Set getActionIds() { if (actionIds == null) { actionIds = this.actions .stream() .map(ResourceActionDefinition::getId) .collect(Collectors.toSet()); } return actionIds; } @JsonIgnore public List getDataAccessAction() { return actions.stream() .filter(act -> CollectionUtils.isNotEmpty(act.getDataAccess().getDataAccessTypes())) .collect(Collectors.toList()); } public boolean hasDataAccessAction() { return actions.stream() .anyMatch(act -> CollectionUtils.isNotEmpty(act.getDataAccess().getDataAccessTypes())); } public boolean hasAction(Collection actions) { if (CollectionUtils.isEmpty(this.actions)) { return true; } if (CollectionUtils.isEmpty(actions)) { return false; } if (logical == Logical.AND) { return getActionIds().containsAll(actions); } return getActionIds().stream().anyMatch(actions::contains); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/define/ResourcesDefinition.java ================================================ package org.hswebframework.web.authorization.define; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import org.apache.commons.collections4.CollectionUtils; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.annotation.Logical; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @Getter @Setter public class ResourcesDefinition { private final Set resources = ConcurrentHashMap.newKeySet(); private Logical logical = Logical.DEFAULT; private Phased phased = Phased.before; public void clear() { resources.clear(); } public void addResource(ResourceDefinition resource, boolean merge) { ResourceDefinition definition = getResource(resource.getId()).orElse(null); if (definition != null) { if (merge) { resource.getActions() .stream() .map(ResourceActionDefinition::copy) .forEach(definition::addAction); } else { resources.remove(definition); } } resources.add(resource.copy()); } public Optional getResource(String id) { return resources .stream() .filter(resource -> resource.getId().equals(id)) .findAny(); } @JsonIgnore public List getDataAccessResources() { return resources .stream() .filter(ResourceDefinition::hasDataAccessAction) .collect(Collectors.toList()); } public boolean hasPermission(Permission permission) { if (CollectionUtils.isEmpty(resources)) { return true; } return getResource(permission.getId()) .filter(resource -> resource.hasAction(permission.getActions())) .isPresent(); } public boolean isEmpty() { return resources.isEmpty(); } public boolean hasPermission(Authentication authentication) { int size = resources.size(); if (size == 0) { return true; } if (size == 1) { for (ResourceDefinition resource : resources) { if (authentication.hasPermission(resource.getId(), resource.getActionIds())) { return true; } } return false; } if (logical == Logical.AND) { return resources .stream() .allMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds())); } return resources .stream() .anyMatch(resource -> authentication.hasPermission(resource.getId(), resource.getActionIds())); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionManager.java ================================================ package org.hswebframework.web.authorization.dimension; import reactor.core.publisher.Flux; import java.util.Collection; /** * 维度管理器 * * @author zhouhao * @since 4.0.12 */ public interface DimensionManager { /** * 获取用户维度 * * @param userId 用户ID * @return 用户维度信息 */ Flux getUserDimension(Collection userId); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserBind.java ================================================ package org.hswebframework.web.authorization.dimension; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class DimensionUserBind implements Externalizable { private static final long serialVersionUID = -6849794470754667710L; private String userId; private String dimensionType; private String dimensionId; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(userId); out.writeUTF(dimensionType); out.writeUTF(dimensionId); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { userId = in.readUTF(); dimensionType = in.readUTF(); dimensionId = in.readUTF(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserBindProvider.java ================================================ package org.hswebframework.web.authorization.dimension; import reactor.core.publisher.Flux; import java.util.Collection; public interface DimensionUserBindProvider { Flux getDimensionBindInfo(Collection userIdList); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/dimension/DimensionUserDetail.java ================================================ package org.hswebframework.web.authorization.dimension; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.authorization.Dimension; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor public class DimensionUserDetail implements Serializable { private static final long serialVersionUID = -6849794470754667710L; private String userId; private List dimensions; public DimensionUserDetail merge(DimensionUserDetail detail) { DimensionUserDetail newDetail = new DimensionUserDetail(); newDetail.setUserId(userId); newDetail.setDimensions(new ArrayList<>()); if (null != dimensions) { newDetail.dimensions.addAll(dimensions); } if (null != detail.getDimensions()) { newDetail.dimensions.addAll(detail.getDimensions()); } return newDetail; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AbstractAuthorizationEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.event.DefaultAsyncEvent; import java.util.Optional; import java.util.function.Function; /** * 抽象授权事件,保存事件常用的数据 * * @author zhouhao * @since 3.0 */ public abstract class AbstractAuthorizationEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -3027505108916079214L; protected String username; protected String password; private final transient Function parameterGetter; /** * 所有参数不能为null * * @param username 用户名 * @param password 密码 * @param parameterGetter 参数获取函数,用户获取授权时传入的参数 */ public AbstractAuthorizationEvent(String username, String password, Function parameterGetter) { if (username == null || password == null || parameterGetter == null) { throw new NullPointerException(); } this.username = username; this.password = password; this.parameterGetter = parameterGetter; } @SuppressWarnings("unchecked") public Optional getParameter(String name) { return Optional.ofNullable((T) parameterGetter.apply(name)); } public String getUsername() { return username; } public String getPassword() { return password; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationBeforeEvent.java ================================================ /* * 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.authorization.events; import lombok.Getter; import org.hswebframework.web.authorization.Authentication; import java.util.function.Function; /** * 授权前事件 * * @author zhouhao * @since 3.0 */ @Getter public class AuthorizationBeforeEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = 5948747533500518524L; private String userId; private Authentication authentication; public AuthorizationBeforeEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public void setAuthorized(String userId) { this.userId = userId; } public void setAuthorized(Authentication authentication) { this.authentication = authentication; } public boolean isAuthorized() { return userId != null || authentication != null; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationDecodeEvent.java ================================================ /* * 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.authorization.events; import java.util.function.Function; /** * 在进行授权时的最开始,触发此事件进行用户名密码解码,解码后请调用{@link #setUsername(String)} {@link #setPassword(String)}重新设置用户名密码 * * @author zhouhao * @since 3.0 */ public class AuthorizationDecodeEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = 5418501934490174251L; public AuthorizationDecodeEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public void setUsername(String username) { super.username = username; } public void setPassword(String password) { super.password = password; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationEvent.java ================================================ /* * 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.authorization.events; /** * 授权事件 * * @author zhouhao * @see AuthorizationSuccessEvent * @see AuthorizationFailedEvent * @see AuthorizationBeforeEvent * @see AuthorizationDecodeEvent * @see AuthorizationExitEvent * @see org.springframework.context.ApplicationEvent * @since 3.0 */ public interface AuthorizationEvent { } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationExitEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; import org.springframework.context.ApplicationEvent; /** * 退出登录事件 * * @author zhouhao */ public class AuthorizationExitEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -4590245933665047280L; private final Authentication authentication; public AuthorizationExitEvent(Authentication authentication) { this.authentication = authentication; } public Authentication getAuthentication() { return authentication; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationFailedEvent.java ================================================ /* * 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.authorization.events; import java.util.function.Function; /** * 授权失败时触发 * * @author zhouhao */ public class AuthorizationFailedEvent extends AbstractAuthorizationEvent { private static final long serialVersionUID = -101792832265740828L; /** * 异常信息 */ private Throwable exception; public AuthorizationFailedEvent(String username, String password, Function parameterGetter) { super(username, password, parameterGetter); } public Throwable getException() { return exception; } public void setException(Throwable exception) { this.exception = exception; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationInitializeEvent.java ================================================ package org.hswebframework.web.authorization.events; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; @Getter @Setter @AllArgsConstructor public class AuthorizationInitializeEvent extends DefaultAsyncEvent { private Authentication authentication; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizationSuccessEvent.java ================================================ /* * 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.authorization.events; import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.event.DefaultAsyncEvent; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; /** * 授权成功事件,当授权成功时,触发此事件,并传入授权的信息 * * @author zhouhao * @see Authentication * @since 3.0 */ public class AuthorizationSuccessEvent extends DefaultAsyncEvent implements AuthorizationEvent { private static final long serialVersionUID = -2452116314154155058L; private final Authentication authentication; private final transient Function parameterGetter; private Map result = new HashMap<>(); public AuthorizationSuccessEvent(Authentication authentication, Function parameterGetter) { this.authentication = authentication; this.parameterGetter = parameterGetter; } public Authentication getAuthentication() { return authentication; } @SuppressWarnings("unchecked") public Optional getParameter(String name) { return Optional.ofNullable((T) parameterGetter.apply(name)); } public Map getResult() { return result; } public void setResult(Map result) { this.result = result; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/events/AuthorizingHandleBeforeEvent.java ================================================ package org.hswebframework.web.authorization.events; import org.hswebframework.web.authorization.define.AuthorizingContext; import org.hswebframework.web.authorization.define.HandleType; import org.hswebframework.web.event.DefaultAsyncEvent; import org.springframework.context.ApplicationEvent; /** * 权限控制事件,在进行权限控制之前会推送此事件,用于自定义权限控制结果: * {@code * @EventListener * public void handleAuthEvent(AuthorizingHandleBeforeEvent e) { * //admin用户可以访问全部操作 * if ("admin".equals(e.getContext().getAuthentication().getUser().getUsername())) { * e.setAllow(true); * } * } * } * * @author zhouhao * @since 4.0 */ public class AuthorizingHandleBeforeEvent extends DefaultAsyncEvent implements AuthorizationEvent { private boolean allow = false; private boolean execute = true; private String message; private final AuthorizingContext context; /** * @deprecated 数据权限控制已取消,4.1版本后移除 */ @Deprecated private final HandleType handleType; public AuthorizingHandleBeforeEvent(AuthorizingContext context, HandleType handleType) { this.context = context; this.handleType = handleType; } public AuthorizingContext getContext() { return context; } public boolean isExecute() { return execute; } public boolean isAllow() { return allow; } /** * 设置通过当前请求 * * @param allow allow */ public void setAllow(boolean allow) { execute = false; this.allow = allow; } public String getMessage() { return message; } /** * 设置错误提示消息 * * @param message 消息 */ public void setMessage(String message) { this.message = message; } /** * @return 权限控制类型 */ public HandleType getHandleType() { return handleType; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AccessDenyException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; import org.hswebframework.web.exception.I18nSupportException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.Set; /** * 权限验证异常 * * @author zhouhao * @since 3.0 */ @ResponseStatus(HttpStatus.FORBIDDEN) @Getter public class AccessDenyException extends I18nSupportException { private static final long serialVersionUID = -5135300127303801430L; private String code; public AccessDenyException() { this("error.access_denied"); } public AccessDenyException(String message) { super(message); } public AccessDenyException(String permission, Set actions) { super("error.permission_denied", permission, actions); } public AccessDenyException(String message, String code) { this(message, code, null); } public AccessDenyException(String message, Throwable cause) { this(message, "access_denied", cause); } public AccessDenyException(String message, String code, Throwable cause) { super(message, cause, code); this.code = code; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends AccessDenyException { public NoStackTrace() { super(); } public NoStackTrace(String message) { super(message); } public NoStackTrace(String permission, Set actions) { super(permission, actions); } public NoStackTrace(String message, String code) { super(message, code); } public NoStackTrace(String message, Throwable cause) { super(message, cause); } public NoStackTrace(String message, String code, Throwable cause) { super(message, code, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/AuthenticationException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; import org.hswebframework.web.exception.I18nSupportException; @Getter public class AuthenticationException extends I18nSupportException { public static String ILLEGAL_PASSWORD = "illegal_password"; public static String USER_DISABLED = "user_disabled"; private final String code; public AuthenticationException(String code) { this(code, "error." + code); } public AuthenticationException(String code, String message) { super(message); this.code = code; } public AuthenticationException(String code, String message, Throwable cause) { super(message, cause); this.code = code; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends AuthenticationException { public NoStackTrace(String code) { super(code); } public NoStackTrace(String code, String message) { super(code, message); } public NoStackTrace(String code, String message, Throwable cause) { super(code, message, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/NeedTwoFactorException.java ================================================ package org.hswebframework.web.authorization.exception; import lombok.Getter; /** * @author zhouhao * @since 3.0.4 */ @Getter public class NeedTwoFactorException extends RuntimeException { private static final long serialVersionUID = 3655980280834947633L; private String provider; public NeedTwoFactorException(String message, String provider) { super(message); this.provider = provider; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/exception/UnAuthorizedException.java ================================================ /* * * * 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.authorization.exception; import lombok.Getter; import org.hswebframework.web.authorization.token.TokenState; import org.hswebframework.web.exception.I18nSupportException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; /** * 未授权异常 * * @author zhouhao * @since 3.0 */ @Getter @ResponseStatus(HttpStatus.UNAUTHORIZED) public class UnAuthorizedException extends I18nSupportException { private static final long serialVersionUID = 2422918455013900645L; private final TokenState state; public UnAuthorizedException() { this(TokenState.expired); } public UnAuthorizedException(TokenState state) { this(state.getText(), state); } public UnAuthorizedException(String message, TokenState state) { super(message); this.state = state; } public UnAuthorizedException(String message, TokenState state, Throwable cause) { super(message, cause); this.state = state; } /** * 不填充线程栈的异常,在一些对线程栈不敏感,且对异常不可控(如: 来自未认证请求产生的异常)的情况下不填充线程栈对性能有利。 */ public static class NoStackTrace extends UnAuthorizedException { public NoStackTrace() { super(); } public NoStackTrace(TokenState state) { super(state); } public NoStackTrace(String message, TokenState state) { super(message, state); } public NoStackTrace(String message, TokenState state, Throwable cause) { super(message, state, cause); } @Override public final synchronized Throwable fillInStackTrace() { return this; } } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingNullValueHolder.java ================================================ package org.hswebframework.web.authorization.setting; import java.util.List; import java.util.Optional; /** * @author zhouhao * @since 1.0.0 */ public class SettingNullValueHolder implements SettingValueHolder { public static final SettingNullValueHolder INSTANCE = new SettingNullValueHolder(); private SettingNullValueHolder() { } @Override public Optional> asList(Class t) { return Optional.empty(); } @Override public Optional as(Class t) { return Optional.empty(); } @Override public Optional asString() { return Optional.empty(); } @Override public Optional asLong() { return Optional.empty(); } @Override public Optional asInt() { return Optional.empty(); } @Override public Optional asDouble() { return Optional.empty(); } @Override public Optional getValue() { return Optional.empty(); } @Override public UserSettingPermission getPermission() { return UserSettingPermission.NONE; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/SettingValueHolder.java ================================================ package org.hswebframework.web.authorization.setting; import java.util.List; import java.util.Optional; public interface SettingValueHolder { SettingValueHolder NULL = SettingNullValueHolder.INSTANCE; Optional> asList(Class t); Optional as(Class t); Optional asString(); Optional asLong(); Optional asInt(); Optional asDouble(); Optional getValue(); UserSettingPermission getPermission(); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/StringSourceSettingHolder.java ================================================ package org.hswebframework.web.authorization.setting; import com.alibaba.fastjson.JSON; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.utils.StringUtils; import org.hswebframework.web.dict.EnumDict; import java.util.List; import java.util.Optional; /** * @author zhouhao * @since 3.0.4 */ @AllArgsConstructor @Getter public class StringSourceSettingHolder implements SettingValueHolder { private String value; private UserSettingPermission permission; public static SettingValueHolder of(String value, UserSettingPermission permission) { if (value == null) { return SettingValueHolder.NULL; } return new StringSourceSettingHolder(value, permission); } @Override public Optional> asList(Class t) { return getNativeValue() .map(v -> JSON.parseArray(v, t)); } protected T convert(String value, Class t) { if (t.isEnum()) { if (EnumDict.class.isAssignableFrom(t)) { T val = (T) EnumDict.find((Class) t, value).orElse(null); if (null != val) { return val; } } for (T enumConstant : t.getEnumConstants()) { if (((Enum) enumConstant).name().equalsIgnoreCase(value)) { return enumConstant; } } } return JSON.parseObject(value, t); } @Override @SuppressWarnings("all") public Optional as(Class t) { if (t == String.class) { return (Optional) asString(); } else if (Long.class == t || long.class == t) { return (Optional) asLong(); } else if (Integer.class == t || int.class == t) { return (Optional) asInt(); } else if (Double.class == t || double.class == t) { return (Optional) asDouble(); } return getNativeValue().map(v -> convert(v, t)); } @Override public Optional asString() { return getNativeValue(); } @Override public Optional asLong() { return getNativeValue().map(StringUtils::toLong); } @Override public Optional asInt() { return getNativeValue().map(StringUtils::toInt); } @Override public Optional asDouble() { return getNativeValue().map(StringUtils::toDouble); } private Optional getNativeValue() { return Optional.ofNullable(value); } @Override public Optional getValue() { return Optional.ofNullable(value); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingManager.java ================================================ package org.hswebframework.web.authorization.setting; /** * @author zhouhao * @since 3.0.4 */ public interface UserSettingManager { SettingValueHolder getSetting(String userId, String key); void saveSetting(String userId, String key, String value, UserSettingPermission permission); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/setting/UserSettingPermission.java ================================================ package org.hswebframework.web.authorization.setting; import lombok.AllArgsConstructor; import lombok.Getter; import org.hswebframework.web.dict.Dict; import org.hswebframework.web.dict.EnumDict; /** * @author zhouhao * @since 3.0.4 */ @AllArgsConstructor @Getter @Dict("user-setting-permission") public enum UserSettingPermission implements EnumDict { NONE("无"), R("读"), W("写"), RW("读写"); private String text; @Override public String getValue() { return name(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/AbstractDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.DataAccessConfig; /** * @author zhouhao * @see DataAccessConfig * @since 3.0 */ public abstract class AbstractDataAccessConfig implements DataAccessConfig { private static final long serialVersionUID = -9025349704771557106L; private String action; @Override public String getAction() { return action; } public void setAction(String action) { this.action = action; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/CompositeReactiveAuthenticationManager.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hswebframework.web.authorization.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; @AllArgsConstructor @Slf4j public class CompositeReactiveAuthenticationManager implements ReactiveAuthenticationManager { private final List providers; @Override public Mono authenticate(Mono request) { return Flux .concat( providers .stream() .map(manager -> manager .authenticate(request) .onErrorResume((err) -> { log.warn("get user authenticate error", err); return Mono.empty(); })) .collect(Collectors.toList())) .take(1) .next(); } @Override public Mono getByUserId(String userId) { if (providers.size() == 1) { return providers.get(0).getByUserId(userId); } return Flux .fromStream(providers .stream() .map(manager -> manager .getByUserId(userId) .onErrorResume((err) -> { log.warn("get user [{}] authentication error", userId, err); return Mono.empty(); }) )) .flatMap(Function.identity()) .as(AuthenticationUtils::merge); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.*; import org.hswebframework.web.authorization.builder.AuthenticationBuilderFactory; import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory; import org.hswebframework.web.authorization.dimension.DimensionManager; import org.hswebframework.web.authorization.dimension.DimensionUserBindProvider; 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.*; import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager; import org.hswebframework.web.authorization.twofactor.defaults.DefaultTwoFactorValidatorManager; import org.hswebframework.web.convert.CustomMessageConverter; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; /** * @author zhouhao */ @AutoConfiguration public class DefaultAuthorizationAutoConfiguration { @Bean @ConditionalOnMissingBean(UserTokenManager.class) @ConfigurationProperties(prefix = "hsweb.user-token") public UserTokenManager userTokenManager() { return new DefaultUserTokenManager(); } @Bean @ConditionalOnMissingBean // @ConditionalOnBean(ReactiveAuthenticationManagerProvider.class) public ReactiveAuthenticationManager reactiveAuthenticationManager(List providers) { return new CompositeReactiveAuthenticationManager(providers); } @Bean @ConditionalOnBean(ReactiveAuthenticationManager.class) public UserTokenReactiveAuthenticationSupplier userTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager, ReactiveAuthenticationManager authenticationManager) { UserTokenReactiveAuthenticationSupplier supplier = new UserTokenReactiveAuthenticationSupplier(userTokenManager, authenticationManager); ReactiveAuthenticationHolder.addSupplier(supplier); return supplier; } @Bean @ConditionalOnBean(AuthenticationManager.class) public UserTokenAuthenticationSupplier userTokenAuthenticationSupplier(UserTokenManager userTokenManager, AuthenticationManager authenticationManager) { UserTokenAuthenticationSupplier supplier = new UserTokenAuthenticationSupplier(userTokenManager, authenticationManager); AuthenticationHolder.addSupplier(supplier); return supplier; } @Bean @ConditionalOnMissingBean(DataAccessConfigBuilderFactory.class) @ConfigurationProperties(prefix = "hsweb.authorization.data-access", ignoreInvalidFields = true) public SimpleDataAccessConfigBuilderFactory dataAccessConfigBuilderFactory() { return new SimpleDataAccessConfigBuilderFactory(); } @Bean @ConditionalOnMissingBean(AuthenticationBuilderFactory.class) public AuthenticationBuilderFactory authenticationBuilderFactory(DataAccessConfigBuilderFactory dataAccessConfigBuilderFactory) { return new SimpleAuthenticationBuilderFactory(dataAccessConfigBuilderFactory); } @Bean public CustomMessageConverter authenticationCustomMessageConverter(AuthenticationBuilderFactory factory) { return new CustomMessageConverter() { @Override public boolean support(Class clazz) { return clazz == Authentication.class; } @Override public Object convert(Class clazz, byte[] message) { String json = new String(message); return factory.create().json(json).build(); } }; } @Bean @ConditionalOnMissingBean(DimensionManager.class) public DimensionManager defaultDimensionManager(ObjectProviderbindProviders, ObjectProvider providers){ DefaultDimensionManager manager = new DefaultDimensionManager(); bindProviders.forEach(manager::addBindProvider); providers.forEach(manager::addProvider); return manager; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultDimensionManager.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionProvider; import org.hswebframework.web.authorization.dimension.DimensionManager; import org.hswebframework.web.authorization.dimension.DimensionUserBind; import org.hswebframework.web.authorization.dimension.DimensionUserBindProvider; import org.hswebframework.web.authorization.dimension.DimensionUserDetail; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; import java.util.stream.Collectors; public class DefaultDimensionManager implements DimensionManager { private final List dimensionProviders = new CopyOnWriteArrayList<>(); private final List bindProviders = new CopyOnWriteArrayList<>(); private final Mono> providerMapping = Flux .defer(() -> Flux.fromIterable(dimensionProviders)) .flatMap(provider -> provider .getAllType() .map(type -> Tuples.of(type.getId(), provider))) .collectMap(Tuple2::getT1, Tuple2::getT2); public DefaultDimensionManager() { } public void addProvider(DimensionProvider provider) { dimensionProviders.add(provider); } public void addBindProvider(DimensionUserBindProvider bindProvider) { bindProviders.add(bindProvider); } private Mono> providerMapping() { return providerMapping; } @Override public Flux getUserDimension(Collection userId) { return this .providerMapping() .flatMapMany(providerMapping -> Flux .fromIterable(bindProviders) //获取绑定信息 .flatMap(provider -> provider.getDimensionBindInfo(userId)) .groupBy(DimensionUserBind::getDimensionType) .flatMap(group -> { String type = group.key(); Flux binds = group.cache(); DimensionProvider provider = providerMapping.get(type); if (null == provider) { return Mono.empty(); } //获取维度信息 return binds .map(DimensionUserBind::getDimensionId) .collect(Collectors.toSet()) .flatMapMany(idList -> provider.getDimensionsById(SimpleDimensionType.of(type), idList)) .collectMap(Dimension::getId, Function.identity()) .flatMapMany(mapping -> binds .groupBy(DimensionUserBind::getUserId) .flatMap(userGroup -> Mono .zip( Mono.just(userGroup.key()), userGroup .handle((bind, sink) -> { Dimension dimension = mapping.get(bind.getDimensionId()); if (dimension != null) { sink.next(dimension); } }) .collectList(), DimensionUserDetail::of )) ); }) ) .groupBy(DimensionUserDetail::getUserId) .flatMap(group->group.reduce(DimensionUserDetail::merge)); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DimensionDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.DimensionType; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DefaultDataAccessType; import org.hswebframework.web.authorization.access.ScopeDataAccessConfig; import org.hswebframework.web.authorization.simple.AbstractDataAccessConfig; import java.util.Set; @Getter @Setter @EqualsAndHashCode(callSuper = true) public class DimensionDataAccessConfig extends AbstractDataAccessConfig implements ScopeDataAccessConfig { private Set scope; private boolean children; /** * @see DimensionType#getId() */ private String scopeType; @Override public DefaultDataAccessType getType() { return DefaultDataAccessType.DIMENSION_SCOPE; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/PlainTextUsernamePasswordAuthenticationRequest.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import org.hswebframework.web.authorization.AuthenticationRequest; /** * @author zhouhao * @since 3.0.0-RC */ @Getter @Setter @AllArgsConstructor @NoArgsConstructor public class PlainTextUsernamePasswordAuthenticationRequest implements AuthenticationRequest { private String username; private String password; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java ================================================ /* * 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.authorization.simple; import lombok.Getter; import lombok.Setter; import org.hswebframework.web.authorization.*; import java.io.Serial; import java.io.Serializable; import java.util.*; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; public class SimpleAuthentication implements Authentication { static final AtomicLongFieldUpdater ACCESS_COUNT_UPDATER = AtomicLongFieldUpdater.newUpdater(SimpleAuthentication.class, "accessCount"); @Serial private static final long serialVersionUID = -2898863220255336528L; @Getter private User user; @Setter private List permissions = new ArrayList<>(); private List dimensions = new ArrayList<>(); @Setter private Map attributes = new HashMap<>(); public static Authentication of() { return new SimpleAuthentication(); } @Override @SuppressWarnings("unchecked") public Optional getAttribute(String name) { return Optional.ofNullable((T) attributes.get(name)); } public List getDimensions() { return dimensions == null ? Collections.emptyList() : dimensions; } public List getPermissions() { return permissions == null ? Collections.emptyList() : permissions; } @Override public Map getAttributes() { return attributes == null ? Collections.emptyMap() : attributes; } public SimpleAuthentication merge(Authentication authentication) { Map mePermissionGroup = permissions .stream() .collect(Collectors.toMap(Permission::getId, Function.identity())); if (authentication.getUser() != null) { user = authentication.getUser(); } this.attributes = new HashMap<>(getAttributes()); this.attributes.putAll(authentication.getAttributes()); this.permissions = new ArrayList<>(this.getPermissions()); for (Permission permission : authentication.getPermissions()) { Permission me = mePermissionGroup.get(permission.getId()); if (me == null) { permissions.add(permission.copy()); continue; } me.getActions().addAll(permission.getActions()); } this.dimensions = new ArrayList<>(this.getDimensions()); for (Dimension dimension : authentication.getDimensions()) { if (getDimension(dimension.getType(), dimension.getId()).isEmpty()) { dimensions.add(dimension); } } return this; } protected SimpleAuthentication newInstance() { return new SimpleAuthentication(); } @Override public Authentication copy(BiPredicate permissionFilter, Predicate dimension) { SimpleAuthentication authentication = newInstance(); 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()) ); if (user != null) { authentication.setUser0(user); } authentication.setAttributes(new HashMap<>(attributes)); return authentication; } public void setUser(User user) { this.user = user; dimensions.add(user); } protected void setUser0(User user) { this.user = user; } public void setDimensions(List dimensions) { this.dimensions.addAll(dimensions); } public void setDimensions(Collection dimensions) { this.dimensions.addAll(dimensions); } public void addDimension(Dimension dimension) { this.dimensions.add(dimension); } private transient volatile Map> dimensionMapping; private transient volatile Map permissionMapping; private transient volatile long accessCount; protected boolean fastPath() { // 总共访问超过8次,则进行初始化缓存. if (ACCESS_COUNT_UPDATER.incrementAndGet(this) == 8) { if (permissionMapping == null) { permissionMapping = permissions == null ? Collections.emptyMap() : permissions .stream() .collect(Collectors .toMap(Permission::getId, Function.identity(), (a, b) -> b)); dimensionMapping = dimensions == null ? Collections.emptyMap() : dimensions .stream() .collect(Collectors .groupingBy(d -> d.getType().getId(), Collectors.toMap( Dimension::getId, Function.identity(), (a, b) -> a))); } } return permissionMapping != null; } @Override public boolean hasPermission(String permissionId, Collection actions) { Map permissionMapping = this.permissionMapping; if (fastPath() && permissionMapping != null) { Permission permission = permissionMapping.get(permissionId); if (permission == null) { permission = permissionMapping.get("*"); } if (permission == null) { return false; } return actions.isEmpty() || permission.getActions().containsAll(actions) || permission.getActions().contains("*"); } return Authentication.super.hasPermission(permissionId, actions); } @Override public Optional getDimension(String type, String id) { Map> dimensionMapping = this.dimensionMapping; if (fastPath() && dimensionMapping != null) { Map mapping = dimensionMapping.get(type); if (mapping == null) { return Optional.empty(); } return Optional.ofNullable(mapping.get(id)); } return Authentication.super.getDimension(type, id); } @Override public Optional getDimension(DimensionType type, String id) { return getDimension(type.getId(), id); } @Override public List getDimensions(DimensionType type) { return this.getDimensions(type.getId()); } @Override public List getDimensions(String type) { Map> dimensionMapping = this.dimensionMapping; if (fastPath() && dimensionMapping != null) { Map mapping = dimensionMapping.get(type); if (mapping == null) { return List.of(); } return new ArrayList<>(mapping.values()); } return Authentication.super.getDimensions(type); } @Override public Optional getPermission(String id) { Map permissionMapping = this.permissionMapping; if (fastPath() && permissionMapping != null) { return Optional.ofNullable(permissionMapping.get(id)); } return Authentication.super.getPermission(id); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleDimension.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.DimensionType; import java.util.Map; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor @EqualsAndHashCode public class SimpleDimension implements Dimension { private String id; private String name; private DimensionType type; private Map options; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleDimensionType.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.DimensionType; import java.io.Serializable; @Getter @Setter @AllArgsConstructor(staticName = "of") @NoArgsConstructor @EqualsAndHashCode public class SimpleDimensionType implements DimensionType, Serializable { private static final long serialVersionUID = -6849794470754667710L; private String id; private String name; public static SimpleDimensionType of(String id) { return of(id, id); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleFieldFilterDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.DataAccessType; import org.hswebframework.web.authorization.access.DefaultDataAccessType; import org.hswebframework.web.authorization.access.FieldFilterDataAccessConfig; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import static org.hswebframework.web.authorization.access.DataAccessConfig.DefaultType.DENY_FIELDS; /** * 默认配置实现 * * @author zhouhao * @see FieldFilterDataAccessConfig * @since 3.0 */ public class SimpleFieldFilterDataAccessConfig extends AbstractDataAccessConfig implements FieldFilterDataAccessConfig { private static final long serialVersionUID = 8080660575093151866L; private Set fields; public SimpleFieldFilterDataAccessConfig() { } public SimpleFieldFilterDataAccessConfig(String... fields) { this.fields = new HashSet<>(Arrays.asList(fields)); } @Override public Set getFields() { return fields; } public void setFields(Set fields) { this.fields = fields; } @Override public DataAccessType getType() { return DefaultDataAccessType.FIELD_DENY; } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleOwnCreatedDataAccessConfig.java ================================================ package org.hswebframework.web.authorization.simple; import org.hswebframework.web.authorization.access.OwnCreatedDataAccessConfig; /** * @author zhouhao * @since 3.0 */ public class SimpleOwnCreatedDataAccessConfig extends AbstractDataAccessConfig implements OwnCreatedDataAccessConfig { private static final long serialVersionUID = -6059330812806119730L; public SimpleOwnCreatedDataAccessConfig() { } public SimpleOwnCreatedDataAccessConfig(String action) { setAction(action); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.apache.commons.collections4.CollectionUtils; 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 */ @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(exclude = "dataAccesses") public class SimplePermission implements Permission { private static final long serialVersionUID = 7587266693680162184L; private String id; private String name; private Set actions; private Set dataAccesses; private Map options; public Set getActions() { if (actions == null) { actions = new java.util.HashSet<>(); } return actions; } public Set getDataAccesses() { if (dataAccesses == null) { dataAccesses = new java.util.HashSet<>(); } return dataAccesses; } @Override public Permission copy(Predicate actionFilter, Predicate dataAccessFilter) { SimplePermission permission = new SimplePermission(); permission.setId(id); permission.setName(name); 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); } @Override public String toString() { return id + (CollectionUtils.isNotEmpty(actions) ? ":" + String.join(",", actions) : ""); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleRole.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.Dimension; import org.hswebframework.web.authorization.Role; import java.io.Serializable; import java.util.Map; /** * @author zhouhao */ @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode public class SimpleRole implements Role { private static final long serialVersionUID = 7460859165231311347L; private String id; private String name; private Map options; public static Role of(Dimension dimension) { return SimpleRole.builder() .name(dimension.getName()) .id(dimension.getId()) .options(dimension.getOptions()) .build(); } } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleUser.java ================================================ package org.hswebframework.web.authorization.simple; import lombok.*; import org.hswebframework.web.authorization.User; import java.io.Serial; import java.io.Serializable; import java.util.Map; /** * @author zhouhao */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @EqualsAndHashCode public class SimpleUser implements User { @Serial private static final long serialVersionUID = 2194541828191869091L; private String id; private String username; private String name; private String userType; private Map options; } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/builder/DataAccessConfigConverter.java ================================================ package org.hswebframework.web.authorization.simple.builder; import org.hswebframework.web.authorization.access.DataAccessConfig; /** * @author zhouhao */ public interface DataAccessConfigConverter { boolean isSupport(String type, String action, String config); DataAccessConfig convert(String type, String action, String config); } ================================================ FILE: hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/builder/SimpleAuthenticationBuilder.java ================================================ package org.hswebframework.web.authorization.simple.builder; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Maps; import org.hswebframework.web.authorization.*; import org.hswebframework.web.authorization.builder.AuthenticationBuilder; import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory; import org.hswebframework.web.authorization.simple.*; import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; /** * @author zhouhao */ public class SimpleAuthenticationBuilder implements AuthenticationBuilder { private SimpleAuthentication authentication = new SimpleAuthentication(); private DataAccessConfigBuilderFactory dataBuilderFactory; public SimpleAuthenticationBuilder(DataAccessConfigBuilderFactory dataBuilderFactory) { this.dataBuilderFactory = dataBuilderFactory; } public void setDataBuilderFactory(DataAccessConfigBuilderFactory dataBuilderFactory) { this.dataBuilderFactory = dataBuilderFactory; } @Override public AuthenticationBuilder user(User user) { Objects.requireNonNull(user); authentication.setUser(user); return this; } @Override public AuthenticationBuilder user(String user) { return user(JSON.parseObject(user, SimpleUser.class)); } @Override public AuthenticationBuilder user(Map user) { Objects.requireNonNull(user.get("id")); user(SimpleUser.builder() .id(user.get("id")) .username(user.get("username")) .name(user.get("name")) .userType(user.get("type")) .build()); return this; } @Override public AuthenticationBuilder role(List role) { authentication.getDimensions().addAll(role); return this; } @Override @SuppressWarnings("unchecked") public AuthenticationBuilder role(String role) { return role((List) JSON.parseArray(role, SimpleRole.class)); } @Override public AuthenticationBuilder permission(List permission) { authentication.setPermissions(permission); return this; } public AuthenticationBuilder permission(JSONArray jsonArray) { List permissions = new ArrayList<>(); for (int i = 0; i < jsonArray.size(); i++) { JSONObject jsonObject = jsonArray.getJSONObject(i); SimplePermission permission = new SimplePermission(); permission.setId(jsonObject.getString("id")); permission.setName(jsonObject.getString("name")); permission.setOptions(jsonObject.getJSONObject("options")); JSONArray actions = jsonObject.getJSONArray("actions"); if (actions != null) { permission.setActions(new HashSet<>(actions.toJavaList(String.class))); } JSONArray dataAccess = jsonObject.getJSONArray("dataAccesses"); if (null != dataAccess) { permission.setDataAccesses(dataAccess.stream().map(JSONObject.class::cast) .map(dataJson -> dataBuilderFactory .create() .fromJson(dataJson.toJSONString()) .build()) .filter(Objects::nonNull) .collect(Collectors.toSet())); } permissions.add(permission); } authentication.setPermissions(permissions); return this; } @Override public AuthenticationBuilder permission(String permissionJson) { return permission(JSON.parseArray(permissionJson)); } @Override public AuthenticationBuilder attributes(String attributes) { authentication.getAttributes().putAll(JSON.>parseObject(attributes, Map.class)); return this; } @Override public AuthenticationBuilder attributes(Map permission) { authentication.getAttributes().putAll(permission); return this; } public AuthenticationBuilder dimension(JSONArray json) { if (json == null) { return this; } List dimensions = new ArrayList<>(); for (int i = 0; i < json.size(); i++) { JSONObject jsonObject = json.getJSONObject(i); Object type = jsonObject.get("type"); Map