jmix-framework / jmix

Jmix framework
https://www.jmix.io
Apache License 2.0
487 stars 112 forks source link

Enhancing RequestMatcher building for the resource server configuration #3398

Open gorbunkov opened 2 weeks ago

gorbunkov commented 2 weeks ago

The issue: #3397

Implementation Details

New modules have been added to Jmix: io.jmix.security:jmix-security-resource-server and io.jmix.security:jmix-security-resource-server-starter. New modules contains classes for constructing the request matcher for the resource server.

jmix-authserver-starterand jmix-oidc-starter depend on the jmix-security-resource-server-starter.

Resource server configurations in OIDC and Authorization Server add-on starters now use the CompositeResourceServerRequestMatcherProvider to get the ReqeustMatcher that will be used a securityMatcher for the HttpSecurity instance:

        @Bean("authsr_ResourceServerSecurityFilterChain")
        @Order(JmixSecurityFilterChainOrder.AUTHSERVER_RESOURCE_SERVER)
        public SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http,
                                                                     OpaqueTokenIntrospector opaqueTokenIntrospector,
                                                                     ApplicationEventPublisher applicationEventPublisher,
                                                                     CompositeResourceServerRequestMatcherProvider securityMatcherProvider) throws Exception {
            RequestMatcher authenticatedRequestMatcher = securityMatcherProvider.getAuthenticatedRequestMatcher();
            RequestMatcher anonymousRequestMatcher = securityMatcherProvider.getAnonymousRequestMatcher();
            RequestMatcher securityMatcher = new OrRequestMatcher(authenticatedRequestMatcher, anonymousRequestMatcher);
            http
                    .securityMatcher(securityMatcher)
                    .authorizeHttpRequests(authorize -> {
                        authorize
                                .requestMatchers(anonymousRequestMatcher).permitAll()
                                .requestMatchers(authenticatedRequestMatcher).authenticated();
                    })

            //...
       }

The default implementation of the CompositeResourceServerRequestMatcherProvider combines RequestMatchers provided by multiple AuthenticatedRequestMatcherProvider and AnonymousRequestMatcherProvider instances.

If necessary, users may define their own implementation of the CompositeResourceServerRequestMatcherProvider in the project and this implementation will be used instead of default one.

Configure RequestMatchers for the Resource Server in the Project

Creating the AuthenticatedRequestMatcherProvider

import io.jmix.securityresourceserver.requestmatcher.ResourceServerRequestMatcherProvider;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;

@Component
public class GreetingAuthenticatedRequestMatcherProvider implements AuthenticatedRequestMatcherProvider {

    @Override
    public RequestMatcher getAuthenticatedRequestMatcher() {
        return new AntPathRequestMatcher("/greeting/**");
    }
}

This approach allows you to create complex request matchers, e.g. new AntPathRequestMatcher("/greeting/**", HttpMethod.GET.name());

Creating the AnonymousRequestMatcherProvider

If some URL processed by the resource server must be accessed anonymously, the AnonymousRequestMatcherProvider may be used.

import io.jmix.securityresourceserver.requestmatcher.AnonymousRequestMatcherProvider;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;

@Component
public class GreetingAnonymousRequestMatcherProvider implements AnonymousRequestMatcherProvider {
    @Override
    public RequestMatcher getAnonymousRequestMatcher() {
        return new AntPathRequestMatcher("/greeting/public/**");
    }
}

Creating the AuthenticatedUrlPatternsProvider

import io.jmix.securityresourceserver.requestmatcher.urlprovider.AuthenticatedUrlPatternsProvider;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class GreetingAuthenticatedUrlProvider implements AuthenticatedUrlPatternsProvider {

    @Override
    public List<String> getAuthenticatedUrlPatterns() {
        return List.of("/greeting/**");
    }
}

This approach allows you to return a list of URL patterns that must be protected by the resource server.

Creating the AnonymousUrlPatternsProvider

import io.jmix.securityresourceserver.requestmatcher.urlprovider.AnonymousUrlPatternsProvider;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class GreetingAnonymousUrlProvider implements AnonymousUrlPatternsProvider {
    @Override
    public List<String> getAnonymousUrlPatterns() {
        return List.of("/greeting/public/**");
    }
}

This approach allows you to return a list of URL patterns that must be processed by the resource server configuration but accessed anonymously.

Using the Application Property

jmix.resource-server.authenticated-url-patterns = /first/**, /second/**
jmix.resource-server.anonymous-url-patterns = /api1/public/**, /api2/**

Legacy AuthorizedUrlsProvider Support

Legacy AuthorizedUrlsProvider will continue working:

import io.jmix.core.security.AuthorizedUrlsProvider;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.List;

@Component
public class LegacyAuthorizedUrlsProvider implements AuthorizedUrlsProvider {
    @Override
    public Collection<String> getAuthenticatedUrlPatterns() {
        return List.of("/legacy/**");
    }

    @Override
    public Collection<String> getAnonymousUrlPatterns() {
        return List.of();
    }
}

Generic REST Add-on Endpoints Security

Previously, Generic REST add-on endpoints were secured by default:

After the changes made in this PR, REST endpoints security must be explicitly defined in the project, for example:

jmix.resource-server.authenticated-url-patterns = /rest/**

This settings may be added by Studio when it migrates old project or when the REST API is added to the project.

Application properties from the REST API add-on should be replaced with corresponding resource server properties:

jmix.rest.anonymous-url-patterns=
jmix.rest.authenticated-url-patterns=
jmix.resource-server.anonymous-url-patterns=
jmix.resource-server.authenticated-url-patterns=