增加部分实现

This commit is contained in:
zhou-hao
2019-10-12 20:00:35 +08:00
parent 3b586cbbfd
commit 8ce58b3bec
53 changed files with 1079 additions and 428 deletions

View File

@@ -49,9 +49,11 @@ public final class AuthenticationHolder {
private static final ReadWriteLock lock = new ReentrantReadWriteLock();
private static Optional<Authentication> get(Function<AuthenticationSupplier, Authentication> function) {
private static Optional<Authentication> get(Function<AuthenticationSupplier, Optional<Authentication>> function) {
return Flux.fromStream(suppliers.stream().map(function))
.filter(Optional::isPresent)
.map(Optional::get)
.reduceWith(CompositeAuthentication::new, CompositeAuthentication::merge)
.filter(CompositeAuthentication::isNotEmpty)
.map(Authentication.class::cast)

View File

@@ -18,10 +18,7 @@
package org.hswebframework.web.authorization;
import reactor.core.publisher.Mono;
import java.io.Serializable;
import java.util.Map;
import java.util.Optional;
/**
* 授权信息管理器,用于获取用户授权和同步授权信息
@@ -37,7 +34,7 @@ public interface AuthenticationManager {
* @param request 授权请求
* @return 授权成功则返回用户权限信息
*/
Mono<Authentication> authenticate(Mono<AuthenticationRequest> request);
Authentication authenticate(AuthenticationRequest request);
/**
* 根据用户ID获取权限信息
@@ -45,7 +42,7 @@ public interface AuthenticationManager {
* @param userId 用户ID
* @return 权限信息
*/
Mono<Authentication> getByUserId(String userId);
Optional<Authentication> getByUserId(String userId);
}

View File

@@ -19,6 +19,7 @@ package org.hswebframework.web.authorization;
import reactor.core.publisher.Mono;
import java.util.Optional;
import java.util.function.Supplier;
/**
@@ -27,6 +28,7 @@ import java.util.function.Supplier;
* @see Authentication
* @see ReactiveAuthenticationHolder
*/
public interface AuthenticationSupplier extends Supplier<Authentication> {
Authentication get(String userId);
public interface AuthenticationSupplier extends Supplier<Optional<Authentication>> {
Optional<Authentication> get(String userId);
}

View File

@@ -27,7 +27,7 @@ import reactor.core.publisher.Mono;
* @author zhouhao
* @since 4.0
*/
public interface AuthenticationInitializeService {
public interface ReactiveAuthenticationInitializeService {
/**
* 根据用户ID初始化权限信息
*

View File

@@ -0,0 +1,48 @@
/*
* 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 reactor.core.publisher.Mono;
/**
* 授权信息管理器,用于获取用户授权和同步授权信息
*
* @author zhouhao
* @see 3.0
*/
public interface ReactiveAuthenticationManager {
/**
* 进行授权操作
*
* @param request 授权请求
* @return 授权成功则返回用户权限信息
*/
Mono<Authentication> authenticate(Mono<AuthenticationRequest> request);
/**
* 根据用户ID获取权限信息
*
* @param userId 用户ID
* @return 权限信息
*/
Mono<Authentication> getByUserId(String userId);
}

View File

@@ -30,7 +30,6 @@ import java.io.Serializable;
* @author zhouhao
* @see CustomDataAccessConfig
* @see OwnCreatedDataAccessConfig
* @see ScriptDataAccessConfig
*/
public interface DataAccessConfig extends Serializable {
@@ -72,19 +71,12 @@ public interface DataAccessConfig extends Serializable {
String FIELD_SCOPE = "FIELD_SCOPE";
/**
* 字段过滤,黑名单
* 禁止操作字段
*
* @see FieldFilterDataAccessConfig#getType()
*/
String DENY_FIELDS = "DENY_FIELDS";
/**
* 自定义脚本方式
*
* @see ScriptDataAccessConfig#getType()
*/
String SCRIPT = "SCRIPT";
/**
* 自定义控制器
*

View File

@@ -1,28 +0,0 @@
package org.hswebframework.web.authorization.access;
/**
* 通过脚本来控制数据操作权限.脚本可以在前端设置角色的时候进行编辑
*
* @author zhouhao
*/
public interface ScriptDataAccessConfig extends DataAccessConfig {
@Override
default String getType() {
return DefaultType.SCRIPT;
}
/**
* 脚本语言: javascript(js),groovy
*
* @return 语言
*/
String getScriptLanguage();
/**
* 脚本内容,在进行验证的时候会执行脚本
*
* @return 脚本
*/
String getScript();
}

View File

@@ -2,6 +2,8 @@ package org.hswebframework.web.authorization.builder;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import java.util.Map;
/**
*
* @author zhouhao
@@ -9,5 +11,7 @@ import org.hswebframework.web.authorization.access.DataAccessConfig;
public interface DataAccessConfigBuilder {
DataAccessConfigBuilder fromJson(String json);
DataAccessConfigBuilder fromMap(Map<String,Object> json);
DataAccessConfig build();
}

View File

@@ -1,14 +1,13 @@
package org.hswebframework.web.authorization.simple;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.ReactiveAuthenticationHolder;
import org.hswebframework.web.authorization.AuthenticationManager;
import org.hswebframework.web.authorization.*;
import org.hswebframework.web.authorization.builder.AuthenticationBuilderFactory;
import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory;
import org.hswebframework.web.authorization.simple.builder.DataAccessConfigConvert;
import org.hswebframework.web.authorization.simple.builder.SimpleAuthenticationBuilderFactory;
import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfigBuilderFactory;
import org.hswebframework.web.authorization.token.DefaultUserTokenManager;
import org.hswebframework.web.authorization.token.UserTokenAuthenticationSupplier;
import org.hswebframework.web.authorization.token.UserTokenReactiveAuthenticationSupplier;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager;
@@ -40,14 +39,23 @@ public class DefaultAuthorizationAutoConfiguration {
}
@Bean
@ConditionalOnBean(AuthenticationManager.class)
public UserTokenReactiveAuthenticationSupplier userTokenAuthenticationSupplier(UserTokenManager userTokenManager,
AuthenticationManager authenticationManager) {
@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)

View File

@@ -58,6 +58,9 @@ public class SimpleAuthentication implements Authentication {
@Override
public List<Permission> getPermissions() {
if(permissions==null){
return Collections.emptyList();
}
return new ArrayList<>(permissions);
}

View File

@@ -1,21 +0,0 @@
package org.hswebframework.web.authorization.simple;
import lombok.*;
import org.hswebframework.web.authorization.access.ScriptDataAccessConfig;
/**
* @author zhouhao
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SimpleScriptDataAccessConfig extends AbstractDataAccessConfig implements ScriptDataAccessConfig {
private static final long serialVersionUID = 2667127339980983720L;
private String script;
private String scriptLanguage;
}

View File

@@ -5,17 +5,21 @@ import com.alibaba.fastjson.JSONObject;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.builder.DataAccessConfigBuilder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author zhouhao
*/
public class SimpleDataAccessConfigBuilder implements DataAccessConfigBuilder {
private String json;
private List<DataAccessConfigConvert> converts;
private Map<String, Object> config = new HashMap<>();
public SimpleDataAccessConfigBuilder(List<DataAccessConfigConvert> converts) {
Objects.requireNonNull(converts);
this.converts = converts;
@@ -23,14 +27,20 @@ public class SimpleDataAccessConfigBuilder implements DataAccessConfigBuilder {
@Override
public DataAccessConfigBuilder fromJson(String json) {
this.json = json;
config.putAll(JSON.parseObject(json));
return this;
}
@Override
public DataAccessConfigBuilder fromMap(Map<String, Object> map) {
config.putAll(map);
return this;
}
@Override
public DataAccessConfig build() {
Objects.requireNonNull(json);
JSONObject jsonObject = JSON.parseObject(json);
Objects.requireNonNull(config);
JSONObject jsonObject = new JSONObject(config);
String type = jsonObject.getString("type");
String action = jsonObject.getString("action");
@@ -40,7 +50,7 @@ public class SimpleDataAccessConfigBuilder implements DataAccessConfigBuilder {
Objects.requireNonNull(action);
if (config == null) {
config = json;
config = jsonObject.toJSONString();
}
String finalConfig = config;

View File

@@ -81,9 +81,6 @@ public class SimpleDataAccessConfigBuilderFactory implements DataAccessConfigBui
converts.add(createConfig(OWN_CREATED, (action, config) -> new SimpleOwnCreatedDataAccessConfig(action)));
}
if (defaultSupportConvert.contains(SCRIPT)) {
converts.add(createJsonConfig(SCRIPT, SimpleScriptDataAccessConfig.class));
}
if (defaultSupportConvert.contains(CUSTOM)) {
converts.add(createConfig(CUSTOM, (action, config) -> new SimpleCustomDataAccessConfigConfig(config)));

View File

@@ -3,6 +3,8 @@ package org.hswebframework.web.authorization.token;
import org.hswebframework.web.authorization.Authentication;
import reactor.core.publisher.Mono;
import java.util.Optional;
/**
* @author zhouhao
* @since 1.0
@@ -20,6 +22,6 @@ public interface ThirdPartAuthenticationManager {
* @param userId 用户ID
* @return 权限信息
*/
Mono<Authentication> getByUserId(String userId);
Optional<Authentication> getByUserId(String userId);
}

View File

@@ -0,0 +1,25 @@
package org.hswebframework.web.authorization.token;
import org.hswebframework.web.authorization.Authentication;
import reactor.core.publisher.Mono;
/**
* @author zhouhao
* @since 1.0
*/
public interface ThirdPartReactiveAuthenticationManager {
/**
* @return 支持的tokenType
*/
String getTokenType();
/**
* 根据用户ID获取权限信息
*
* @param userId 用户ID
* @return 权限信息
*/
Mono<Authentication> getByUserId(String userId);
}

View File

@@ -0,0 +1,77 @@
package org.hswebframework.web.authorization.token;
import org.hswebframework.web.authorization.*;
import org.hswebframework.web.context.ContextKey;
import org.hswebframework.web.context.ContextUtils;
import org.springframework.beans.factory.annotation.Autowired;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* @author zhouhao
*/
public class UserTokenAuthenticationSupplier implements AuthenticationSupplier {
private AuthenticationManager defaultAuthenticationManager;
private UserTokenManager userTokenManager;
private Map<String, ThirdPartAuthenticationManager> thirdPartAuthenticationManager = new HashMap<>();
public UserTokenAuthenticationSupplier(UserTokenManager userTokenManager, AuthenticationManager defaultAuthenticationManager) {
this.defaultAuthenticationManager = defaultAuthenticationManager;
this.userTokenManager = userTokenManager;
}
@Autowired(required = false)
public void setThirdPartAuthenticationManager(List<ThirdPartAuthenticationManager> thirdPartReactiveAuthenticationManager) {
for (ThirdPartAuthenticationManager manager : thirdPartReactiveAuthenticationManager) {
this.thirdPartAuthenticationManager.put(manager.getTokenType(), manager);
}
}
@Override
public Optional<Authentication> get(String userId) {
if (userId == null) {
return Optional.empty();
}
return get(this.defaultAuthenticationManager, userId);
}
protected Optional<Authentication> get(ThirdPartAuthenticationManager authenticationManager, String userId) {
if (null == userId) {
return Optional.empty();
}
if (null == authenticationManager) {
return this.defaultAuthenticationManager.getByUserId(userId);
}
return authenticationManager.getByUserId(userId);
}
protected Optional<Authentication> get(AuthenticationManager authenticationManager, String userId) {
if (null == userId) {
return Optional.empty();
}
if (null == authenticationManager) {
authenticationManager = this.defaultAuthenticationManager;
}
return authenticationManager.getByUserId(userId);
}
@Override
public Optional<Authentication> get() {
return ContextUtils.currentContext()
.get(ContextKey.of(ParsedToken.class))
.map(t -> userTokenManager.getByToken(t.getToken()))
.map(tokenMono -> tokenMono
.map(token -> get(thirdPartAuthenticationManager.get(token.getType()), token.getUserId()))
.flatMap(Mono::justOrEmpty))
.flatMap(Mono::blockOptional);
}
}

View File

@@ -1,7 +1,7 @@
package org.hswebframework.web.authorization.token;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.AuthenticationManager;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier;
import org.hswebframework.web.context.ContextKey;
import org.hswebframework.web.context.ContextUtils;
@@ -17,20 +17,20 @@ import java.util.Map;
*/
public class UserTokenReactiveAuthenticationSupplier implements ReactiveAuthenticationSupplier {
private AuthenticationManager defaultAuthenticationManager;
private ReactiveAuthenticationManager defaultAuthenticationManager;
private UserTokenManager userTokenManager;
private Map<String, ThirdPartAuthenticationManager> thirdPartAuthenticationManager = new HashMap<>();
private Map<String, ThirdPartReactiveAuthenticationManager> thirdPartAuthenticationManager = new HashMap<>();
public UserTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager, AuthenticationManager defaultAuthenticationManager) {
public UserTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager, ReactiveAuthenticationManager defaultAuthenticationManager) {
this.defaultAuthenticationManager = defaultAuthenticationManager;
this.userTokenManager=userTokenManager;
}
@Autowired(required = false)
public void setThirdPartAuthenticationManager(List<ThirdPartAuthenticationManager> thirdPartAuthenticationManager) {
for (ThirdPartAuthenticationManager manager : thirdPartAuthenticationManager) {
public void setThirdPartAuthenticationManager(List<ThirdPartReactiveAuthenticationManager> thirdPartReactiveAuthenticationManager) {
for (ThirdPartReactiveAuthenticationManager manager : thirdPartReactiveAuthenticationManager) {
this.thirdPartAuthenticationManager.put(manager.getTokenType(), manager);
}
}
@@ -43,7 +43,7 @@ public class UserTokenReactiveAuthenticationSupplier implements ReactiveAuthenti
return get(this.defaultAuthenticationManager, userId);
}
protected Mono<Authentication> get(ThirdPartAuthenticationManager authenticationManager, String userId) {
protected Mono<Authentication> get(ThirdPartReactiveAuthenticationManager authenticationManager, String userId) {
if (null == userId) {
return null;
}
@@ -53,7 +53,7 @@ public class UserTokenReactiveAuthenticationSupplier implements ReactiveAuthenti
return authenticationManager.getByUserId(userId);
}
protected Mono<Authentication> get(AuthenticationManager authenticationManager, String userId) {
protected Mono<Authentication> get(ReactiveAuthenticationManager authenticationManager, String userId) {
if (null == userId) {
return null;
}
@@ -69,7 +69,8 @@ public class UserTokenReactiveAuthenticationSupplier implements ReactiveAuthenti
.flatMap(context ->
context.get(ContextKey.of(ParsedToken.class))
.map(t -> userTokenManager.getByToken(t.getToken()))
.map(tokenMono -> tokenMono.flatMap(token -> get(thirdPartAuthenticationManager.get(token.getType()), token.getUserId())))
.map(tokenMono -> tokenMono
.flatMap(token -> get(thirdPartAuthenticationManager.get(token.getType()), token.getUserId())))
.orElseGet(Mono::empty));
}

View File

@@ -96,7 +96,7 @@ public class AuthenticationTests {
.build();
//初始化权限管理器,用于获取用户的权限信息
AuthenticationManager authenticationManager = new AuthenticationManager() {
ReactiveAuthenticationManager authenticationManager = new ReactiveAuthenticationManager() {
@Override
public Mono<Authentication> authenticate(Mono<AuthenticationRequest> request) {
return Mono.empty();

View File

@@ -1,10 +1,12 @@
package org.hswebframework.web.authorization.basic.configuration;
import org.hswebframework.web.authorization.AuthenticationManager;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.hswebframework.web.authorization.access.DataAccessController;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.basic.aop.AopMethodAuthorizeDefinitionParser;
import org.hswebframework.web.authorization.basic.embed.EmbedAuthenticationManager;
import org.hswebframework.web.authorization.basic.embed.EmbedAuthenticationProperties;
import org.hswebframework.web.authorization.basic.embed.EmbedReactiveAuthenticationManager;
import org.hswebframework.web.authorization.basic.handler.DefaultAuthorizingHandler;
import org.hswebframework.web.authorization.basic.handler.UserAllowPermissionHandler;
import org.hswebframework.web.authorization.basic.handler.access.DefaultDataAccessController;
@@ -16,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@@ -33,6 +36,7 @@ import java.util.List;
* @since 3.0
*/
@Configuration
@EnableConfigurationProperties(EmbedAuthenticationProperties.class)
public class AuthorizingHandlerAutoConfiguration {
@Bean
@@ -85,19 +89,19 @@ public class AuthorizingHandlerAutoConfiguration {
AopMethodAuthorizeDefinitionParser parser,
List<UserTokenParser> userTokenParser) {
return new WebMvcConfigurerAdapter() {
return new WebMvcConfigurer() {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new WebUserTokenInterceptor(userTokenManager, userTokenParser, parser));
super.addInterceptors(registry);
}
};
}
@Bean
@ConditionalOnMissingBean(AuthenticationManager.class)
public AuthenticationManager embedAuthenticationManager() {
return new EmbedAuthenticationManager();
@ConditionalOnMissingBean(ReactiveAuthenticationManager.class)
public ReactiveAuthenticationManager embedAuthenticationManager(EmbedAuthenticationProperties properties) {
return new EmbedReactiveAuthenticationManager(properties);
}
@Bean
@@ -130,8 +134,9 @@ public class AuthorizingHandlerAutoConfiguration {
}
@Bean
public UserTokenController userTokenController() {
return new UserTokenController();
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public ReactiveUserTokenController userTokenController() {
return new ReactiveUserTokenController();
}
@Configuration

View File

@@ -60,8 +60,8 @@ public class BasicAuthorizationTokenParser implements UserTokenForTypeParser {
if (usernameAndPassword.contains(":")) {
String[] arr = usernameAndPassword.split("[:]");
Authentication authentication = authenticationManager
.authenticate(Mono.just(new PlainTextUsernamePasswordAuthenticationRequest(arr[0], arr[1])))
.blockOptional().orElse(null);
.authenticate(new PlainTextUsernamePasswordAuthenticationRequest(arr[0], arr[1]))
;
if (authentication != null) {
return new AuthorizedToken() {
@Override

View File

@@ -0,0 +1,112 @@
package org.hswebframework.web.authorization.basic.embed;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory;
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
import org.hswebframework.web.authorization.simple.SimplePermission;
import org.hswebframework.web.authorization.simple.SimpleRole;
import org.hswebframework.web.authorization.simple.SimpleUser;
import java.util.*;
import java.util.stream.Collectors;
/**
* <pre>
* hsweb:
* users:
* admin:
* name: 超级管理员
* username: admin
* password: admin
* roles:
* - id: admin
* name: 管理员
* - id: user
* name: 用户
* permissions:
* - id: user-manager
* actions: *
* dataAccesses:
* - action: query
* type: DENY_FIELDS
* fields: password,salt
* </pre>
*
* @author zhouhao
* @since 3.0.0-RC
*/
@Getter
@Setter
public class EmbedAuthenticationInfo {
private String id;
private String name;
private String username;
private String type;
private String password;
private List<SimpleRole> roles = new ArrayList<>();
private List<PermissionInfo> permissions = new ArrayList<>();
private Map<String, List<String>> permissionsSimple = new HashMap<>();
@Getter
@Setter
public static class PermissionInfo {
private String id;
private String name;
private Set<String> actions = new HashSet<>();
private List<Map<String, Object>> dataAccesses = new ArrayList<>();
}
public Authentication toAuthentication(DataAccessConfigBuilderFactory factory) {
SimpleAuthentication authentication = new SimpleAuthentication();
SimpleUser user = new SimpleUser();
user.setId(id);
user.setName(name);
user.setUsername(username);
user.setType(type);
authentication.setUser(user);
authentication.setRoles((List) roles);
List<Permission> permissionList = new ArrayList<>();
permissionList.addAll(permissions.stream()
.map(info -> {
SimplePermission permission = new SimplePermission();
permission.setId(info.getId());
permission.setName(info.getName());
permission.setActions(info.getActions());
permission.setDataAccesses(info.getDataAccesses()
.stream().map(conf -> factory.create()
.fromMap(conf)
.build()).collect(Collectors.toSet()));
return permission;
})
.collect(Collectors.toList()));
permissionList.addAll(permissionsSimple.entrySet().stream()
.map(entry -> {
SimplePermission permission = new SimplePermission();
permission.setId(entry.getKey());
permission.setActions(new HashSet<>(entry.getValue()));
return permission;
}).collect(Collectors.toList()));
authentication.setPermissions(permissionList);
return authentication;
}
}

View File

@@ -1,87 +1,37 @@
package org.hswebframework.web.authorization.basic.embed;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.AuthenticationManager;
import org.hswebframework.web.authorization.AuthenticationRequest;
import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory;
import org.hswebframework.web.authorization.simple.PlainTextUsernamePasswordAuthenticationRequest;
import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfigBuilderFactory;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import javax.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
@ConfigurationProperties(prefix = "hsweb")
@Order(Ordered.HIGHEST_PRECEDENCE)
public class EmbedAuthenticationManager implements AuthenticationManager {
private Map<String, Authentication> authentications = new HashMap<>();
@Autowired(required = false)
private DataAccessConfigBuilderFactory dataAccessConfigBuilderFactory = new SimpleDataAccessConfigBuilderFactory();
@Getter
@Setter
private Map<String, EmbedAuthenticationProperties> users = new HashMap<>();
@PostConstruct
public void init() {
users.forEach((id, properties) -> {
if (StringUtils.isEmpty(properties.getId())) {
properties.setId(id);
}
for (EmbedAuthenticationProperties.PermissionInfo permissionInfo : properties.getPermissions()) {
for (Map<String, Object> objectMap : permissionInfo.getDataAccesses()) {
for (Map.Entry<String, Object> stringObjectEntry : objectMap.entrySet()) {
if (stringObjectEntry.getValue() instanceof Map) {
Map<?, ?> mapVal = ((Map) stringObjectEntry.getValue());
boolean maybeIsList = mapVal.keySet().stream().allMatch(org.hswebframework.utils.StringUtils::isInt);
if (maybeIsList) {
stringObjectEntry.setValue(mapVal.values());
}
}
}
}
}
authentications.put(id, properties.toAuthentication(dataAccessConfigBuilderFactory));
});
}
@Autowired
private EmbedAuthenticationProperties properties;
@Override
public Mono<Authentication> authenticate(Mono<AuthenticationRequest> request) {
return request.filter(r -> r instanceof PlainTextUsernamePasswordAuthenticationRequest)
.map(PlainTextUsernamePasswordAuthenticationRequest.class::cast)
.map(pwdReq -> users.values()
.stream()
.filter(user ->
pwdReq.getUsername().equals(user.getUsername())
&& pwdReq.getPassword().equals(user.getPassword()))
.findFirst()
.map(EmbedAuthenticationProperties::getId)
.map(authentications::get)
.orElseThrow(() -> new ValidationException("用户不存在")));
public Authentication authenticate(AuthenticationRequest request) {
return properties.authenticate(request);
}
@Override
public Mono<Authentication> getByUserId(String userId) {
return Mono.just(authentications.get(userId));
public Optional<Authentication> getByUserId(String userId) {
return properties.getAuthentication(userId);
}
void addAuthentication(Authentication authentication) {
authentications.put(authentication.getUser().getId(), authentication);
}
}

View File

@@ -1,23 +1,27 @@
package org.hswebframework.web.authorization.basic.embed;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.Role;
import org.hswebframework.web.authorization.AuthenticationRequest;
import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory;
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
import org.hswebframework.web.authorization.simple.SimplePermission;
import org.hswebframework.web.authorization.simple.SimpleRole;
import org.hswebframework.web.authorization.simple.SimpleUser;
import org.hswebframework.web.authorization.simple.PlainTextUsernamePasswordAuthenticationRequest;
import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfigBuilderFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;
import java.util.*;
import java.util.stream.Collectors;
import javax.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* <pre>
* hsweb:
* auth:
* users:
* admin:
* name: 超级管理员
@@ -42,72 +46,61 @@ import java.util.stream.Collectors;
*/
@Getter
@Setter
public class EmbedAuthenticationProperties {
@ConfigurationProperties(prefix = "hsweb.auth")
public class EmbedAuthenticationProperties implements InitializingBean {
private String id;
private String name;
private String username;
private String type;
private String password;
private List<SimpleRole> roles = new ArrayList<>();
private List<PermissionInfo> permissions = new ArrayList<>();
private Map<String, List<String>> permissionsSimple = new HashMap<>();
private Map<String, Authentication> authentications = new HashMap<>();
@Getter
@Setter
public static class PermissionInfo {
private String id;
private Map<String, EmbedAuthenticationInfo> users = new HashMap<>();
private String name;
@Autowired(required = false)
private DataAccessConfigBuilderFactory dataAccessConfigBuilderFactory = new SimpleDataAccessConfigBuilderFactory();
private Set<String> actions = new HashSet<>();
private List<Map<String, Object>> dataAccesses = new ArrayList<>();
@Override
public void afterPropertiesSet() {
users.forEach((id, properties) -> {
if (StringUtils.isEmpty(properties.getId())) {
properties.setId(id);
}
for (EmbedAuthenticationInfo.PermissionInfo permissionInfo : properties.getPermissions()) {
for (Map<String, Object> objectMap : permissionInfo.getDataAccesses()) {
for (Map.Entry<String, Object> stringObjectEntry : objectMap.entrySet()) {
if (stringObjectEntry.getValue() instanceof Map) {
Map<?, ?> mapVal = ((Map) stringObjectEntry.getValue());
boolean maybeIsList = mapVal.keySet().stream().allMatch(org.hswebframework.utils.StringUtils::isInt);
if (maybeIsList) {
stringObjectEntry.setValue(mapVal.values());
}
}
}
}
}
authentications.put(id, properties.toAuthentication(dataAccessConfigBuilderFactory));
});
}
public Authentication toAuthentication(DataAccessConfigBuilderFactory factory) {
SimpleAuthentication authentication = new SimpleAuthentication();
SimpleUser user = new SimpleUser();
user.setId(id);
user.setName(name);
user.setUsername(username);
user.setType(type);
authentication.setUser(user);
authentication.setRoles((List) roles);
List<Permission> permissionList = new ArrayList<>();
public Authentication authenticate(AuthenticationRequest request) {
if(request instanceof PlainTextUsernamePasswordAuthenticationRequest){
PlainTextUsernamePasswordAuthenticationRequest pwdReq = ((PlainTextUsernamePasswordAuthenticationRequest) request);
return users.values()
.stream()
.filter(user ->
pwdReq.getUsername().equals(user.getUsername())
&& pwdReq.getPassword().equals(user.getPassword()))
.findFirst()
.map(EmbedAuthenticationInfo::getId)
.map(authentications::get)
.orElseThrow(() -> new ValidationException("用户不存在"));
}
permissionList.addAll(permissions.stream()
.map(info -> {
SimplePermission permission = new SimplePermission();
permission.setId(info.getId());
permission.setName(info.getName());
permission.setActions(info.getActions());
permission.setDataAccesses(info.getDataAccesses()
.stream().map(conf -> factory.create()
.fromJson(JSON.toJSONString(conf))
.build()).collect(Collectors.toSet()));
return permission;
})
.collect(Collectors.toList()));
permissionList.addAll(permissionsSimple.entrySet().stream()
.map(entry -> {
SimplePermission permission = new SimplePermission();
permission.setId(entry.getKey());
permission.setActions(new HashSet<>(entry.getValue()));
return permission;
}).collect(Collectors.toList()));
authentication.setPermissions(permissionList);
return authentication;
throw new UnsupportedOperationException("不支持的授权请求:"+request);
}
public Optional<Authentication> getAuthentication(String userId) {
return Optional.ofNullable(authentications.get(userId));
}
}

View File

@@ -0,0 +1,35 @@
package org.hswebframework.web.authorization.basic.embed;
import lombok.AllArgsConstructor;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.AuthenticationRequest;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import reactor.core.publisher.Mono;
/**
* @author zhouhao
* @since 3.0.0-RC
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@AllArgsConstructor
public class EmbedReactiveAuthenticationManager implements ReactiveAuthenticationManager {
private EmbedAuthenticationProperties properties;
@Override
public Mono<Authentication> authenticate(Mono<AuthenticationRequest> request) {
return request.map(properties::authenticate);
}
@Override
public Mono<Authentication> getByUserId(String userId) {
return Mono.justOrEmpty(properties.getAuthentication(userId));
}
}

View File

@@ -31,7 +31,6 @@ public final class DefaultDataAccessController implements DataAccessController {
}
this.parent = parent;
addHandler(new CustomDataAccessHandler()).
addHandler(new ScriptDataAccessHandler()).
addHandler(new FieldFilterDataAccessHandler()).
addHandler(new FieldScopeDataAccessHandler());
}

View File

@@ -1,41 +0,0 @@
package org.hswebframework.web.authorization.basic.handler.access;
import org.apache.commons.codec.digest.DigestUtils;
import org.hswebframework.expands.script.engine.DynamicScriptEngine;
import org.hswebframework.expands.script.engine.DynamicScriptEngineFactory;
import org.hswebframework.utils.StringUtils;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.access.DataAccessHandler;
import org.hswebframework.web.authorization.access.ScriptDataAccessConfig;
import org.hswebframework.web.authorization.define.AuthorizingContext;
/**
* @author zhouhao
*/
public class ScriptDataAccessHandler implements DataAccessHandler {
@Override
public boolean isSupport(DataAccessConfig access) {
return access instanceof ScriptDataAccessConfig;
}
@Override
public boolean handle(DataAccessConfig access, AuthorizingContext context) {
ScriptDataAccessConfig dataAccess = ((ScriptDataAccessConfig) access);
DynamicScriptEngine engine = DynamicScriptEngineFactory.getEngine(dataAccess.getScriptLanguage());
if (engine == null) {
throw new UnsupportedOperationException(dataAccess.getScriptLanguage() + " {not_support}");
}
String scriptId = DigestUtils.md5Hex(dataAccess.getScript());
try {
if (!engine.compiled(scriptId)) {
engine.compile(scriptId, dataAccess.getScript());
}
Object success = engine.execute(scriptId, context.getParamContext().getParams()).getIfSuccess();
return StringUtils.isTrue(success);
} catch (Exception e) {
throw new BusinessException("{script_error}", e);
}
}
}

View File

@@ -23,6 +23,7 @@ import io.swagger.annotations.ApiParam;
import lombok.SneakyThrows;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.AuthenticationManager;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.authorization.events.*;
import org.hswebframework.web.authorization.simple.PlainTextUsernamePasswordAuthenticationRequest;
@@ -48,7 +49,7 @@ import java.util.function.Function;
public class AuthorizationController {
@Autowired
private AuthenticationManager authenticationManager;
private ReactiveAuthenticationManager authenticationManager;
@Autowired
private ApplicationEventPublisher eventPublisher;

View File

@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiOperation;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.AuthenticationManager;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
import org.hswebframework.web.authorization.token.TokenState;
@@ -22,10 +23,10 @@ import reactor.core.publisher.Mono;
@RequestMapping
@Authorize(permission = "user-token", description = "用户令牌信息管理")
@Api(tags = "权限-用户令牌管理", value = "权限-用户令牌管理")
public class UserTokenController {
public class ReactiveUserTokenController {
private UserTokenManager userTokenManager;
private AuthenticationManager authenticationManager;
private ReactiveAuthenticationManager authenticationManager;
@Autowired
@Lazy
@@ -35,7 +36,7 @@ public class UserTokenController {
@Autowired
@Lazy
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
public void setAuthenticationManager(ReactiveAuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

View File

@@ -1,43 +1,15 @@
package org.hswebframework.web.authorization.basic.aop;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.basic.configuration.EnableAopAuthorize;
import org.hswebframework.web.authorization.basic.web.GeneratedToken;
import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenGenerator;
import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenParser;
import org.hswebframework.web.authorization.token.ParsedToken;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
import org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.Map;
@SpringBootApplication(exclude = {
WebMvcAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class,
DispatcherServletAutoConfiguration.class})
@SpringBootApplication
@EnableAopAuthorize
public class TestApplication {
public static void main(String[] args) {
SpringApplication application=new SpringApplication(TestApplication.class);
application.setApplicationContextClass(ReactiveWebServerApplicationContext.class);
application.run(args);
SpringApplication.run(TestApplication.class,args);
}
}

View File

@@ -1,65 +0,0 @@
package org.hswebframework.web.authorization.basic.aop;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.AuthenticationManager;
import org.hswebframework.web.authorization.User;
import org.hswebframework.web.authorization.basic.web.GeneratedToken;
import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenGenerator;
import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenParser;
import org.hswebframework.web.authorization.simple.DefaultAuthorizationAutoConfiguration;
import org.hswebframework.web.authorization.token.ParsedToken;
import org.hswebframework.web.authorization.token.UserTokenManager;
import org.hswebframework.web.id.IDGenerator;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collections;
import java.util.Map;
@WebFluxTest(FluxTestController.class)
@RunWith(SpringRunner.class)
@Import(DefaultAuthorizationAutoConfiguration.class)
public class WebFluxTests {
@Autowired
private WebTestClient client;
@Autowired
private UserTokenManager tokenManager;
@Test
public void test(){
tokenManager.signIn("test","test-token","admin",10000).block();
client.get().uri("/test")
.header("token","test")
.exchange()
.expectStatus()
.isOk();
}
}

View File

@@ -1,5 +1,6 @@
hsweb:
users:
auth:
users:
admin:
username: admin
password: admin

View File

@@ -29,6 +29,7 @@
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -9,6 +9,8 @@ import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
import org.hswebframework.web.api.crud.entity.ImplementFor;
import org.hswebframework.web.crud.annotation.Reactive;
import org.hswebframework.web.api.crud.entity.GenericEntity;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -138,13 +140,21 @@ public class EasyormRepositoryRegistrar implements ImportBeanDefinitionRegistrar
registry.registerBeanDefinition(realType.getSimpleName().concat("SyncRepository"), definition);
}
RootBeanDefinition definition = new RootBeanDefinition();
definition.setTargetType(AutoDDLProcessor.class);
definition.setBeanClass(AutoDDLProcessor.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.getPropertyValues().add("entities", entityInfos);
definition.getPropertyValues().add("reactive", reactive);
registry.registerBeanDefinition(AutoDDLProcessor.class.getName(), definition);
try {
BeanDefinition definition = registry.getBeanDefinition(AutoDDLProcessor.class.getName());
Set<EntityInfo> infos = (Set) definition.getPropertyValues().get("entities");
infos.addAll(entityInfos);
} catch (NoSuchBeanDefinitionException e) {
RootBeanDefinition definition = new RootBeanDefinition();
definition.setTargetType(AutoDDLProcessor.class);
definition.setBeanClass(AutoDDLProcessor.class);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
definition.getPropertyValues().add("entities", entityInfos);
definition.getPropertyValues().add("reactive", reactive);
registry.registerBeanDefinition(AutoDDLProcessor.class.getName(), definition);
}
}

View File

@@ -0,0 +1,49 @@
package org.hswebframework.web.event;
import org.springframework.context.PayloadApplicationEvent;
import org.springframework.core.ResolvableType;
/**
* 动态泛型事件,用于动态发布支持泛型的事件
* <pre>
* //相当于发布事件: EntityModifyEvent&lt;UserEntity&gt;
* eventPublisher
* .publishEvent(new GenericsPayloadApplicationEvent&lt;&gt;(this, new EntityModifyEvent<>(oldEntity, newEntity), UserEntity.class));
*
* //只监听相同泛型事件
* &#064;EventListener
* public handleEvent(EntityModifyEvent&lt;UserEntity&gt; event){
*
* }
* </pre>
*
* @author zhouhao
* @since 3.0.7
*/
public class GenericsPayloadApplicationEvent<E> extends PayloadApplicationEvent<E> {
private static final long serialVersionUID = 3745888943307798710L;
//泛型列表
private transient Class[] generics;
//事件类型
private transient Class eventType;
/**
* @param source 事件源
* @param payload 事件,不能使用匿名内部类
* @param generics 泛型列表
*/
public GenericsPayloadApplicationEvent(Object source, E payload, Class... generics) {
super(source, payload);
this.generics = generics;
this.eventType = payload.getClass();
}
@Override
public ResolvableType getResolvableType() {
return ResolvableType.forClassWithGenerics(PayloadApplicationEvent.class
, ResolvableType.forClassWithGenerics(eventType, generics));
}
}

View File

@@ -1,13 +1,15 @@
package org.hswebframework.web.system.authorization.api.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.*;
import org.hswebframework.web.api.crud.entity.Entity;
import java.util.Map;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ActionEntity implements Entity {
private String action;

View File

@@ -7,10 +7,7 @@ import org.hswebframework.ezorm.rdb.mapping.annotation.Comment;
import org.hswebframework.ezorm.rdb.mapping.annotation.JsonCodec;
import org.hswebframework.web.api.crud.entity.Entity;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.persistence.*;
import java.sql.JDBCType;
import java.util.List;
import java.util.Set;
@@ -23,6 +20,7 @@ import java.util.Set;
public class AuthorizationSettingEntity implements Entity {
@Id
@Column(length = 32)
@GeneratedValue(generator = "md5")
private String id;
@Column(length = 32, nullable = false, updatable = false)
@@ -55,10 +53,15 @@ public class AuthorizationSettingEntity implements Entity {
@Comment("可操作权限")
private Set<String> actions;
@Column
@Column(name = "data_accesses")
@ColumnType(jdbcType = JDBCType.CLOB)
@JsonCodec
@Comment("数据权限")
private List<DataAccessEntity> dataAccesses;
@Column
private Integer priority;
@Column
private Boolean merge;
}

View File

@@ -10,7 +10,7 @@ import java.util.Set;
@Setter
public class DataAccessEntity {
private Set<String> actions;
private String action;
private String type;

View File

@@ -1,7 +1,6 @@
package org.hswebframework.web.system.authorization.api.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.*;
import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType;
import org.hswebframework.ezorm.rdb.mapping.annotation.Comment;
import org.hswebframework.ezorm.rdb.mapping.annotation.JsonCodec;
@@ -9,6 +8,7 @@ import org.hswebframework.web.api.crud.entity.GenericEntity;
import javax.persistence.Column;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import java.sql.JDBCType;
import java.util.List;
import java.util.Map;
@@ -16,6 +16,9 @@ import java.util.Map;
@Table(name = "s_permission")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PermissionEntity extends GenericEntity<String> {
@Column
@@ -26,7 +29,7 @@ public class PermissionEntity extends GenericEntity<String> {
@Comment("说明")
private String describe;
@Column
@Column(nullable = false)
@Comment("状态")
private Byte status;

View File

@@ -67,6 +67,24 @@
<artifactId>r2dbc-h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hswebframework.web</groupId>
<artifactId>hsweb-authorization-api</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -0,0 +1,50 @@
package org.hswebframework.web.system.authorization.defaults.configuration;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.web.authorization.ReactiveAuthenticationInitializeService;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.hswebframework.web.system.authorization.api.UserPermissionDimensionProvider;
import org.hswebframework.web.system.authorization.api.service.reactive.ReactiveUserService;
import org.hswebframework.web.system.authorization.defaults.service.DefaultReactiveAuthenticationInitializeService;
import org.hswebframework.web.system.authorization.defaults.service.DefaultReactiveAuthenticationManager;
import org.hswebframework.web.system.authorization.defaults.service.DefaultReactiveUserService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AuthorizationServiceAutoConfiguration {
// TODO: 2019-10-12 condition reactive enabled
@Configuration
static class ReactiveAuthorizationServiceAutoConfiguration{
@ConditionalOnBean(ReactiveRepository.class)
@Bean
public ReactiveUserService reactiveUserService() {
return new DefaultReactiveUserService();
}
@ConditionalOnMissingBean
@ConditionalOnBean(ReactiveUserService.class)
@Bean
public ReactiveAuthenticationManager reactiveAuthenticationManager() {
return new DefaultReactiveAuthenticationManager();
}
@Bean
@ConditionalOnBean(ReactiveUserService.class)
public ReactiveAuthenticationInitializeService reactiveAuthenticationInitializeService() {
return new DefaultReactiveAuthenticationInitializeService();
}
@Bean
public UserPermissionDimensionProvider userPermissionDimensionProvider(){
return new UserPermissionDimensionProvider();
}
}
}

View File

@@ -0,0 +1,28 @@
package org.hswebframework.web.system.authorization.defaults.configuration;
import org.hswebframework.web.system.authorization.defaults.webflux.WebFluxPermissionController;
import org.hswebframework.web.system.authorization.defaults.webflux.WebFluxUserController;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AuthorizationWebAutoConfiguration {
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public static class WebFluxAuthorizationConfiguration {
@Bean
public WebFluxPermissionController webFluxPermissionController() {
return new WebFluxPermissionController();
}
@Bean
public WebFluxUserController webFluxUserController() {
return new WebFluxUserController();
}
}
}

View File

@@ -0,0 +1,137 @@
package org.hswebframework.web.system.authorization.defaults.service;
import org.apache.commons.collections.CollectionUtils;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.ReactiveAuthenticationInitializeService;
import org.hswebframework.web.authorization.access.DataAccessConfig;
import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFactory;
import org.hswebframework.web.authorization.simple.SimpleAuthentication;
import org.hswebframework.web.authorization.simple.SimplePermission;
import org.hswebframework.web.authorization.simple.SimpleUser;
import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfigBuilderFactory;
import org.hswebframework.web.system.authorization.api.PermissionDimension;
import org.hswebframework.web.system.authorization.api.PermissionDimensionProvider;
import org.hswebframework.web.system.authorization.api.entity.AuthorizationSettingEntity;
import org.hswebframework.web.system.authorization.api.entity.PermissionEntity;
import org.hswebframework.web.system.authorization.api.entity.UserEntity;
import org.hswebframework.web.system.authorization.api.service.reactive.ReactiveUserService;
import org.springframework.beans.factory.annotation.Autowired;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
public class DefaultReactiveAuthenticationInitializeService
implements ReactiveAuthenticationInitializeService {
@Autowired
private ReactiveUserService userService;
@Autowired
private ReactiveRepository<AuthorizationSettingEntity, String> settingRepository;
@Autowired
private ReactiveRepository<PermissionEntity, String> permissionRepository;
@Autowired(required = false)
private DataAccessConfigBuilderFactory builderFactory = new SimpleDataAccessConfigBuilderFactory();
@Autowired(required = false)
private List<PermissionDimensionProvider> dimensionProviders = new ArrayList<>();
@Override
public Mono<Authentication> initUserAuthorization(String userId) {
return doInit(userService.findById(userId));
}
public Mono<Authentication> doInit(Mono<UserEntity> userEntityMono) {
return userEntityMono.flatMap(user -> {
SimpleAuthentication authentication = new SimpleAuthentication();
authentication.setUser(SimpleUser
.builder()
.id(user.getId())
.name(user.getName())
.username(user.getUsername())
.type(user.getType())
.build());
return initPermission(authentication);
});
}
protected Mono<Authentication> initPermission(SimpleAuthentication authentication) {
return Flux.fromIterable(dimensionProviders)
.flatMap(provider -> provider.getDimensionByUserId(authentication.getUser().getId()))
.collectList()
.flatMap(allDimension -> Mono.zip(getAllPermission(),
settingRepository
.createQuery()
.where(AuthorizationSettingEntity::getState, 1)
.in(AuthorizationSettingEntity::getDimension, allDimension
.stream()
.map(PermissionDimension::getId)
.collect(Collectors.toList()))
.fetch()
.collect(Collectors.groupingBy(AuthorizationSettingEntity::getPermission))
, (_p, _s) -> handlePermission(authentication, allDimension, _p, _s)));
}
protected SimpleAuthentication handlePermission(SimpleAuthentication authentication,
List<PermissionDimension> dimensionList,
Map<String, PermissionEntity> permissions,
Map<String, List<AuthorizationSettingEntity>> settings) {
List<Permission> permissionList = new ArrayList<>();
for (PermissionEntity value : permissions.values()) {
List<AuthorizationSettingEntity> permissionSettings = settings.get(value.getId());
if (CollectionUtils.isEmpty(permissionSettings)) {
continue;
}
permissionSettings.sort(Comparator.comparingInt(e -> e.getPriority() == null ? 0 : e.getPriority()));
SimplePermission permission = new SimplePermission();
permission.setId(value.getId());
permission.setName(value.getName());
Map<String, DataAccessConfig> configs = new HashMap<>();
for (AuthorizationSettingEntity permissionSetting : permissionSettings) {
boolean merge = Boolean.TRUE.equals(permissionSetting.getMerge());
if (!merge) {
permission.getActions().clear();
}
if (permissionSetting.getDataAccesses() != null) {
permissionSetting.getDataAccesses()
.stream()
.map(conf -> builderFactory.create().fromMap(conf.getConfig()).build())
.forEach(access -> configs.put(access.getType(), access));
}
permission.getActions().addAll(permissionSetting.getActions());
}
permission.setDataAccesses(new HashSet<>(configs.values()));
permissionList.add(permission);
}
authentication.setPermissions(permissionList);
return authentication;
}
protected Mono<Map<String, PermissionEntity>> getAllPermission() {
return permissionRepository
.createQuery()
.where(PermissionEntity::getStatus, 1)
.fetch()
.collect(Collectors.toMap(PermissionEntity::getId, Function.identity()));
}
}

View File

@@ -0,0 +1,54 @@
package org.hswebframework.web.system.authorization.defaults.service;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.AuthenticationRequest;
import org.hswebframework.web.authorization.ReactiveAuthenticationInitializeService;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.hswebframework.web.authorization.exception.AccessDenyException;
import org.hswebframework.web.authorization.simple.PlainTextUsernamePasswordAuthenticationRequest;
import org.hswebframework.web.system.authorization.api.entity.UserEntity;
import org.hswebframework.web.system.authorization.api.service.reactive.ReactiveUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.SimpleValueWrapper;
import reactor.core.publisher.Mono;
public class DefaultReactiveAuthenticationManager implements ReactiveAuthenticationManager {
@Autowired
private ReactiveUserService reactiveUserService;
@Autowired
private ReactiveAuthenticationInitializeService initializeService;
@Autowired(required = false)
private CacheManager cacheManager;
@Override
public Mono<Authentication> authenticate(Mono<AuthenticationRequest> request) {
return request
.filter(PlainTextUsernamePasswordAuthenticationRequest.class::isInstance)
.switchIfEmpty(Mono.error(() -> new UnsupportedOperationException("不支持的请求类型")))
.map(PlainTextUsernamePasswordAuthenticationRequest.class::cast)
.flatMap(pwdRequest -> reactiveUserService.findByUsernameAndPassword(pwdRequest.getUsername(), pwdRequest.getPassword()))
.switchIfEmpty(Mono.error(() -> new AccessDenyException("密码错误")))
.map(UserEntity::getId)
.flatMap(this::getByUserId);
}
@Override
public Mono<Authentication> getByUserId(String userId) {
return Mono.justOrEmpty(userId)
.flatMap(_id -> Mono.justOrEmpty(cacheManager)
.map(cm -> cm.getCache("user-auth"))
.flatMap(cache -> Mono.justOrEmpty(cache.get(userId))
.switchIfEmpty(initializeService.initUserAuthorization(_id)
.doOnNext(autz -> cache.put(userId, autz))
.map(SimpleValueWrapper::new)))
.flatMap(valueWrapper -> Mono.justOrEmpty(valueWrapper.get())))
.cast(Authentication.class)
.switchIfEmpty(initializeService.initUserAuthorization(userId))
.cache();
}
}

View File

@@ -0,0 +1,72 @@
package org.hswebframework.web.system.authorization.defaults.webflux;
import org.apache.commons.collections.CollectionUtils;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
import org.hswebframework.web.api.crud.entity.QueryParamEntity;
import org.hswebframework.web.authorization.Permission;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.hswebframework.web.exception.NotFoundException;
import org.hswebframework.web.system.authorization.api.entity.PermissionEntity;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
@RestController
@RequestMapping("/permission")
@Authorize(permission = "permission", description = "权限管理")
public class WebFluxPermissionController {
@Autowired
private ReactiveRepository<PermissionEntity, String> repository;
@GetMapping
@Authorize(action = Permission.ACTION_QUERY)
public Flux<PermissionEntity> findAllPermission(QueryParamEntity paramEntity) {
return repository.createQuery()
.setParam(paramEntity)
.fetch();
}
@GetMapping("/{id}")
@Authorize(action = Permission.ACTION_QUERY)
public Mono<PermissionEntity> getPermission(@PathVariable String id) {
return Mono.just(id)
.as(repository::findById)
.switchIfEmpty(Mono.error(NotFoundException::new));
}
@GetMapping("/count")
@Authorize(action = Permission.ACTION_QUERY)
public Mono<Integer> countAllPermission(QueryParamEntity paramEntity) {
return repository.createQuery()
.setParam(paramEntity)
.count()
.defaultIfEmpty(0);
}
@PatchMapping
@Authorize(action = Permission.ACTION_UPDATE)
public Mono<SaveResult> savePermission(@RequestBody Publisher<PermissionEntity> paramEntity) {
return repository.save(paramEntity);
}
@PutMapping("/status/{status}")
@Authorize(action = Permission.ACTION_UPDATE)
public Mono<Integer> changePermissionState(@PathVariable Byte status, @RequestBody List<String> idList) {
return Mono.just(idList)
.filter(CollectionUtils::isNotEmpty)
.flatMap(list -> repository.createUpdate()
.set(PermissionEntity::getStatus, status)
.where()
.in(PermissionEntity::getId, list)
.execute())
.defaultIfEmpty(0);
}
}

View File

@@ -0,0 +1,14 @@
package org.hswebframework.web.system.authorization.defaults.webflux;
import org.hswebframework.web.authorization.annotation.Authorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
@Authorize(permission = "user",description = "用户管理")
public class WebFluxUserController {
}

View File

@@ -0,0 +1,4 @@
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.hswebframework.web.system.authorization.defaults.configuration.AuthorizationServiceAutoConfiguration,\
org.hswebframework.web.system.authorization.defaults.configuration.AuthorizationWebAutoConfiguration

View File

@@ -0,0 +1,96 @@
package org.hswebframework.web.system.authorization.defaults.service.reactive;
import org.hswebframework.ezorm.rdb.mapping.ReactiveRepository;
import org.hswebframework.web.authorization.Authentication;
import org.hswebframework.web.authorization.ReactiveAuthenticationManager;
import org.hswebframework.web.authorization.User;
import org.hswebframework.web.authorization.simple.PlainTextUsernamePasswordAuthenticationRequest;
import org.hswebframework.web.system.authorization.api.entity.ActionEntity;
import org.hswebframework.web.system.authorization.api.entity.AuthorizationSettingEntity;
import org.hswebframework.web.system.authorization.api.entity.PermissionEntity;
import org.hswebframework.web.system.authorization.api.entity.UserEntity;
import org.hswebframework.web.system.authorization.api.service.reactive.ReactiveUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.Arrays;
import java.util.Collections;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ReactiveTestApplication.class)
public class DefaultReactiveAuthenticationManagerTest {
@Autowired
private ReactiveUserService userService;
@Autowired
private ReactiveAuthenticationManager reactiveAuthenticationManager;
@Autowired
private ReactiveRepository<PermissionEntity, String> permissionRepository;
@Autowired
private ReactiveRepository<AuthorizationSettingEntity, String> settingRepository;
@Test
public void test() {
UserEntity entity = new UserEntity();
entity.setName("admin");
entity.setUsername("admin");
entity.setPassword("admin");
userService.saveUser(Mono.just(entity))
.as(StepVerifier::create)
.expectNext(true)
.verifyComplete();
permissionRepository.newInstance()
.map(permission -> {
permission.setId("test");
permission.setName("测试");
permission.setActions(Arrays.asList(ActionEntity.builder().action("add").describe("新增").build()));
permission.setStatus((byte) 1);
return permission;
})
.as(permissionRepository::insert)
.as(StepVerifier::create)
.expectNext(1)
.verifyComplete();
settingRepository.newInstance()
.map(setting -> {
setting.setPermission("test");
setting.setActions(Collections.singleton("add"));
setting.setDimension(entity.getId());
setting.setDimensionName("测试用户");
setting.setState((byte) 1);
return setting;
})
.as(settingRepository::insert)
.as(StepVerifier::create)
.expectNext(1)
.verifyComplete();
Mono<Authentication> authenticationMono = reactiveAuthenticationManager
.authenticate(Mono.just(new PlainTextUsernamePasswordAuthenticationRequest("admin", "admin")))
.cache();
authenticationMono.map(Authentication::getUser)
.map(User::getName)
.as(StepVerifier::create)
.expectNext("admin")
.verifyComplete();
authenticationMono.map(autz->autz.hasPermission("test","add"))
.as(StepVerifier::create)
.expectNext(true)
.verifyComplete();
}
}

View File

@@ -1,6 +1,9 @@
package org.hswebframework.web.system.authorization.defaults.service.reactive;
import org.hswebframework.web.crud.configuration.JdbcSqlExecutorConfiguration;
import org.hswebframework.web.system.authorization.api.UserPermissionDimensionProvider;
import org.hswebframework.web.system.authorization.defaults.service.DefaultReactiveAuthenticationInitializeService;
import org.hswebframework.web.system.authorization.defaults.service.DefaultReactiveAuthenticationManager;
import org.hswebframework.web.system.authorization.defaults.service.DefaultReactiveUserService;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -18,10 +21,4 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
public class ReactiveTestApplication {
@Bean
public DefaultReactiveUserService defaultReactiveUserService(){
return new DefaultReactiveUserService();
}
}

View File

@@ -0,0 +1,59 @@
package org.hswebframework.web.system.authorization.defaults.service.reactive;
import org.hswebframework.web.crud.annotation.EnableEasyormRepository;
import org.hswebframework.web.crud.configuration.EasyOrmConfiguration;
import org.hswebframework.web.crud.configuration.JdbcSqlExecutorConfiguration;
import org.hswebframework.web.crud.configuration.R2dbcSqlExecutorConfiguration;
import org.hswebframework.web.system.authorization.api.entity.PermissionEntity;
import org.hswebframework.web.system.authorization.defaults.configuration.AuthorizationWebAutoConfiguration;
import org.hswebframework.web.system.authorization.defaults.webflux.WebFluxPermissionController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.reactive.ReactiveTransactionAutoConfiguration;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import reactor.core.publisher.Mono;
@RunWith(SpringRunner.class)
@WebFluxTest(WebFluxPermissionController.class)
@ImportAutoConfiguration(value = {
AuthorizationWebAutoConfiguration.class, EasyOrmConfiguration.class,
R2dbcSqlExecutorConfiguration.class, ConnectionFactoryAutoConfiguration.class,
R2dbcTransactionManagerAutoConfiguration.class,
ReactiveTransactionAutoConfiguration.class
},exclude = {
JdbcSqlExecutorConfiguration.class,
TransactionAutoConfiguration.class
})
@EnableTransactionManagement
public class WebFluxPermissionControllerTest {
@Autowired
WebTestClient client;
@Test
public void test(){
byte[] data=client.get()
.uri("/permission/count")
//.contentType(MediaType.APPLICATION_JSON)
// .body(Mono.just(PermissionEntity
// .builder()
// .name("test")
// .build()),PermissionEntity.class)
.exchange()
.expectBody()
.returnResult()
.getResponseBody();
System.out.println(new String(data));
}
}

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hsweb-system-authorization</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-system-authorization-starter</artifactId>
</project>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>hsweb-system-authorization</artifactId>
<groupId>org.hswebframework.web</groupId>
<version>4.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hsweb-system-authorization-web</artifactId>
</project>

View File

@@ -13,8 +13,6 @@
<description>业务模块-权限管理</description>
<modules>
<module>hsweb-system-authorization-api</module>
<module>hsweb-system-authorization-starter</module>
<module>hsweb-system-authorization-web</module>
<module>hsweb-system-authorization-default</module>
</modules>
<artifactId>hsweb-system-authorization</artifactId>

View File

@@ -268,7 +268,11 @@
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>