简体   繁体   English

FF4J - Spring Boot - 自定义授权管理器

[英]FF4J - Spring Boot - Custom Authorization Manager

I am trying to create a standalone feature flag server (centrally managed feature flag micro-service) backed by spring boot starters provided by FF4J.我正在尝试创建一个由 FF4J 提供的 Spring Boot 启动器支持的独立功能标志服务器(集中管理的功能标志微服务)。 I was able to get it up and running with the web-console and REST API as well.我也能够使用网络控制台和 REST API 启动并运行它。 I am now trying to just add the support of custom authorization manager as provided in the wiki, but based on the sample provided there, I am unclear as to how the authorization manager would be aware of the user context when it gets accessed from a different microservice which is implementing the feature.我现在正在尝试只添加 wiki 中提供的自定义授权管理器的支持,但根据那里提供的示例,我不清楚授权管理器在从不同的用户上下文访问时如何知道用户上下文正在实现该功能的微服务。 Below I have provided all the relevant code snippets.下面我提供了所有相关的代码片段。 If you notice in CustomAuthorizationManager class, I have a currentUserThreadLocal variable, not sure how or who is going to set that at run time for FF4J to verify the user's role.如果您在CustomAuthorizationManager类中注意到,我有一个currentUserThreadLocal变量,不确定如何或谁将在运行时为 FF4J 设置该变量以验证用户的角色。 Any help on this is really appreciated, as I having issues understanding how this works.对此的任何帮助都非常感谢,因为我在理解其工作原理时遇到了问题。

Also note, there is a toJson method in authorization manager that needs to be overridden, not sure what needs to go over there, any help with that is also appreciated.另请注意,授权管理器中有一个toJson方法需要被覆盖,不确定需要在那里做什么,对此也有任何帮助。

Custom Authorization Manager自定义授权管理器

public class CustomAuthorizationManager implements AuthorizationsManager {

    private static final Logger LOG = LoggerFactory.getLogger(FeatureFlagServerFeignTimeoutProperties.class);
    private ThreadLocal<String> currentUserThreadLocal = new ThreadLocal<String>();
    private List<UserRoleBean> userRoles;

    @Autowired
    private SecurityServiceFeignClient securityServiceFeignClient;

    @PostConstruct
    public void init() {
        try {
            userRoles = securityServiceFeignClient.fetchAllUserRoles();
        } catch (Exception ex) {
            LOG.error("Error while loading user roles", ex);
            userRoles = new ArrayList<>();
        }
    }

    @Override
    public String getCurrentUserName() {
        return currentUserThreadLocal.get();
    }

    @Override
    public Set<String> getCurrentUserPermissions() {
        String currentUser = getCurrentUserName();

        Set<String> roles = new HashSet<>();
        if (userRoles.size() != 0) {
            roles = userRoles.stream().filter(userRole -> userRole.getUserLogin().equals(currentUser))
                    .map(userRole -> userRole.getRoleName()).collect(Collectors.toSet());
        } else {
            LOG.warn(
                    "No user roles available, check startup logs to check possible errors during loading of user roles, returning empty");
        }

        return roles;
    }

    @Override
    public Set<String> listAllPermissions() {
        Set<String> roles = new HashSet<>();
        if (userRoles.size() != 0) {
            roles = userRoles.stream().map(userRole -> userRole.getRoleName()).collect(Collectors.toSet());
        } else {
            LOG.warn(
                    "No user roles available, check startup logs to check possible errors during loading of user roles, returning empty");
        }
        return roles;
    }

    @Override
    public String toJson() {
        return null;
    }

}

FF4J config FF4J 配置

@Configuration
@ConditionalOnClass({ ConsoleServlet.class, FF4jDispatcherServlet.class })
public class Ff4jConfig extends SpringBootServletInitializer {

    @Autowired
    private DataSource dataSource;

    @Bean
    public ServletRegistrationBean<FF4jDispatcherServlet> ff4jDispatcherServletRegistrationBean(
            FF4jDispatcherServlet ff4jDispatcherServlet) {
        ServletRegistrationBean<FF4jDispatcherServlet> bean = new ServletRegistrationBean<FF4jDispatcherServlet>(
                ff4jDispatcherServlet, "/feature-web-console/*");
        bean.setName("ff4j-console");
        bean.setLoadOnStartup(1);
        return bean;
    }

    @Bean
    @ConditionalOnMissingBean
    public FF4jDispatcherServlet getFF4jDispatcherServlet() {
        FF4jDispatcherServlet ff4jConsoleServlet = new FF4jDispatcherServlet();
        ff4jConsoleServlet.setFf4j(getFF4j());
        return ff4jConsoleServlet;
    }

    @Bean
    public FF4j getFF4j() {
        FF4j ff4j = new FF4j();
        ff4j.setFeatureStore(new FeatureStoreSpringJdbc(dataSource));
        ff4j.setPropertiesStore(new PropertyStoreSpringJdbc(dataSource));
        ff4j.setEventRepository(new EventRepositorySpringJdbc(dataSource));

        // Set authorization
        CustomAuthorizationManager custAuthorizationManager = new CustomAuthorizationManager();
        ff4j.setAuthorizationsManager(custAuthorizationManager);

        // Enable audit mode
        ff4j.audit(true);

        return ff4j;
    }

}

pom.xml pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>feature-flag-server</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>feature-flag-server</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.RC2</spring-cloud.version>
        <ff4j.version>1.8.2</ff4j.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <exclusions>
                <!-- resolve swagger dependency issue - start -->
                <exclusion>
                    <groupId>com.google.guava</groupId>
                    <artifactId>guava</artifactId>
                </exclusion>
                <!-- resolve swagger dependency issue - end -->
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- FF4J dependencies - start -->
        <dependency>
            <groupId>org.ff4j</groupId>
            <artifactId>ff4j-spring-boot-starter</artifactId>
            <version>${ff4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.ff4j</groupId>
            <artifactId>ff4j-store-springjdbc</artifactId>
            <version>${ff4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.ff4j</groupId>
            <artifactId>ff4j-web</artifactId>
            <version>${ff4j.version}</version>
        </dependency>
        <!-- FF4J dependencies - end -->
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Full disclosure I am the maintainer of the framework.完全披露我是该框架的维护者。

The documentation is not good on this part, improvements are in progress.这部分的文档不好,正在改进中。 But here is some explanation for a working project.但这里是对一个工作项目的一些解释。

When using AuthorizationManager :使用AuthorizationManager

  1. AuthorizationManager principle should be used only if you already enabled authentication in your application (LOGIN FORM, ROLES...).仅当您已在应用程序中启用身份验证(登录表单、角色...)时,才应使用AuthorizationManager原则。 If not you can think about FlipStrategy to create your own predicates.如果没有,您可以考虑 FlipStrategy 来创建您自己的谓词。

  2. FF4j will rely on existing security frameworks to retrieve context of logged user, this is called the principal . FF4j 将依赖existing security frameworks来检索登录用户的上下文,这称为principal As such this is unlikely for you to create your own custom implementation of AuthorizationManager except you are building your own authentication mechanism.因此,除非您正在构建自己的身份验证机制,否则您不太可能创建自己的AuthorizationManager自定义实现。

What to do:该怎么办:

You will use well known framework such as Spring Security of Apache Shiro to secure your applications and simply tell ff4j to rely on it.您将使用众所周知的框架(例如 Apache Shiro 的 Spring Security)来保护您的应用程序,并简单地告诉ff4j依赖它。

How to do:怎么做:

Here is working example using SPRING SECURITY: https://github.com/ff4j/ff4j-samples/tree/master/spring-boot-2x/ff4j-sample-security-spring这是使用 SPRING SECURITY 的工作示例: https : //github.com/ff4j/ff4j-samples/tree/master/spring-boot-2x/ff4j-sample-security-spring

Here is working example using APACHE SHIRO: https://github.com/ff4j/ff4j-samples/tree/master/spring-boot-2x/ff4j-sample-security-shiro这是使用 APACHE SHIRO 的工作示例: https : //github.com/ff4j/ff4j-samples/tree/master/spring-boot-2x/ff4j-sample-security-shiro

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM