Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2017-2026 original authors
*
* 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
*
* https://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 io.micronaut.security.filters;

import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.Internal;
import io.micronaut.http.HttpMethod;
import io.micronaut.http.HttpRequest;
import io.micronaut.security.rules.ConfigurationInterceptUrlMapRule;
import io.micronaut.web.router.RouteAttributes;
import io.micronaut.web.router.resource.StaticResourceResolver;
import jakarta.inject.Singleton;

/**
* Default static-resource-based implementation of {@link StaticResourceAuthenticationBypass}.
*/
@Internal
@Requires(classes = { HttpRequest.class, StaticResourceResolver.class })
@Requires(beans = { ConfigurationInterceptUrlMapRule.class, StaticResourceResolver.class })
@Requires(missingBeans = StaticResourceAuthenticationBypass.class)
@Singleton
final class DefaultStaticResourceAuthenticationBypass implements StaticResourceAuthenticationBypass<HttpRequest<?>> {

private final StaticResourceResolver staticResourceResolver;
private final ConfigurationInterceptUrlMapRule interceptUrlMapRule;

DefaultStaticResourceAuthenticationBypass(StaticResourceResolver staticResourceResolver,
ConfigurationInterceptUrlMapRule interceptUrlMapRule) {
this.staticResourceResolver = staticResourceResolver;
this.interceptUrlMapRule = interceptUrlMapRule;
}

/**
* A route always takes precedence over a static resource. For an unmatched GET or HEAD
* request, resolve the resource before consulting the intercept URL map.
*
* @param request The current request
* @return Whether authentication resolution can be skipped
*/
@Override
public boolean shouldBypass(HttpRequest<?> request) {
if (!(request.getMethod() == HttpMethod.GET || request.getMethod() == HttpMethod.HEAD)) {
return false;
}
if (RouteAttributes.getRouteMatch(request).isPresent()) {
return false;
}
return staticResourceResolver.resolve(request.getUri().getPath()).isPresent()
&& interceptUrlMapRule.isAnonymous(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import io.micronaut.security.rules.SecurityRule;
import io.micronaut.security.rules.SecurityRuleResult;
import io.micronaut.web.router.RouteMatch;
import jakarta.inject.Inject;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -62,7 +63,7 @@
/**
* The attribute used to store the authentication object in the request.
*/
public static final CharSequence AUTHENTICATION = HttpAttributes.PRINCIPAL.toString();

Check warning on line 66 in security/src/main/java/io/micronaut/security/filters/SecurityFilter.java

View workflow job for this annotation

GitHub Actions / build

[removal] PRINCIPAL in HttpAttributes has been deprecated and marked for removal

Check warning on line 66 in security/src/main/java/io/micronaut/security/filters/SecurityFilter.java

View workflow job for this annotation

GitHub Actions / build

[removal] HttpAttributes in io.micronaut.http has been deprecated and marked for removal

/**
* The attribute used to store if the request was rejected and why.
Expand All @@ -85,18 +86,45 @@
protected final Collection<AuthenticationFetcher<HttpRequest<?>>> authenticationFetchers;

protected final SecurityConfiguration securityConfiguration;
private final StaticResourceAuthenticationBypass<HttpRequest<?>> staticResourceAuthenticationBypass;
private final boolean staticResourceAuthenticationBypassEnabled;

/**
* @param securityRules The list of security rules that will allow or reject the request
* @param authenticationFetchers List of {@link AuthenticationFetcher} beans in the context.
* @param securityConfiguration The security configuration
* @deprecated Use {@link #SecurityFilter(Collection, Collection, SecurityConfiguration, SecurityFilterConfiguration, StaticResourceAuthenticationBypass)}.
*/
@Deprecated(forRemoval = true, since = "5.4.0")
public SecurityFilter(Collection<SecurityRule<HttpRequest<?>>> securityRules,

Check warning on line 99 in security/src/main/java/io/micronaut/security/filters/SecurityFilter.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=micronaut-projects_micronaut-security&issues=AaA-FuzM6aTYOMCkTeTm&open=AaA-FuzM6aTYOMCkTeTm&pullRequest=2273
Collection<AuthenticationFetcher<HttpRequest<?>>> authenticationFetchers,
SecurityConfiguration securityConfiguration) {
this.securityRules = securityRules;
this.authenticationFetchers = authenticationFetchers;
this.securityConfiguration = securityConfiguration;
this.staticResourceAuthenticationBypass = null;
this.staticResourceAuthenticationBypassEnabled = false;
}

/**
* @param securityRules The list of security rules that will allow or reject the request
* @param authenticationFetchers List of {@link AuthenticationFetcher} beans in the context
* @param securityConfiguration The security configuration
* @param securityFilterConfiguration The security filter configuration
* @param staticResourceAuthenticationBypass Determines whether authentication can be skipped for static resources
* @since 5.4.0
*/
Comment thread
Copilot marked this conversation as resolved.
@Inject
public SecurityFilter(Collection<SecurityRule<HttpRequest<?>>> securityRules,
Collection<AuthenticationFetcher<HttpRequest<?>>> authenticationFetchers,
SecurityConfiguration securityConfiguration,
SecurityFilterConfiguration securityFilterConfiguration,
@Nullable StaticResourceAuthenticationBypass<HttpRequest<?>> staticResourceAuthenticationBypass) {
this.securityRules = securityRules;
this.authenticationFetchers = authenticationFetchers;
this.securityConfiguration = securityConfiguration;
this.staticResourceAuthenticationBypass = staticResourceAuthenticationBypass;
this.staticResourceAuthenticationBypassEnabled = securityFilterConfiguration.isStaticResourceAuthenticationBypass();
}

@Override
Expand All @@ -108,6 +136,12 @@
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request, ServerFilterChain chain) {
request.getAttributes().put(KEY, true);

if (staticResourceAuthenticationBypassEnabled
&& staticResourceAuthenticationBypass != null
&& staticResourceAuthenticationBypass.shouldBypass(request)) {
return createResponse(null, request, chain);
}

return Flux.fromIterable(authenticationFetchers)
.flatMap(authenticationFetcher -> authenticationFetcher.fetchAuthentication(request))
.next()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,13 @@ public interface SecurityFilterConfiguration extends Toggleable {
*/
@NonNull
String getPattern();

/**
* @return Whether authentication resolution can be bypassed for anonymous static resources
* @since 5.4.0
*/
default boolean isStaticResourceAuthenticationBypass() {
return false;
}
Comment thread
Copilot marked this conversation as resolved.

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public class SecurityFilterConfigurationProperties implements SecurityFilterConf
@SuppressWarnings("WeakerAccess")
public static final boolean DEFAULT_ENABLED = true;

/**
* The default static resource authentication bypass enable value.
*/
public static final boolean DEFAULT_STATIC_RESOURCE_AUTHENTICATION_BYPASS = false;

/**
*
* The pattern the {@link SecurityFilter} should match.
Expand All @@ -47,6 +52,8 @@ public class SecurityFilterConfigurationProperties implements SecurityFilterConf

private boolean enabled = DEFAULT_ENABLED;

private boolean staticResourceAuthenticationBypass = DEFAULT_STATIC_RESOURCE_AUTHENTICATION_BYPASS;

/**
* @return true if you want to enable the {@link SecurityFilter}
*/
Expand All @@ -55,6 +62,11 @@ public boolean isEnabled() {
return this.enabled;
}

@Override
public boolean isStaticResourceAuthenticationBypass() {
return staticResourceAuthenticationBypass;
}

@Override
@NonNull
public String getPattern() {
Expand All @@ -69,6 +81,17 @@ public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

/**
* Enables bypassing authentication resolution for anonymous static resources.
* Default value {@value #DEFAULT_STATIC_RESOURCE_AUTHENTICATION_BYPASS}.
*
* @param staticResourceAuthenticationBypass Whether the bypass is enabled
* @since 5.4.0
*/
public void setStaticResourceAuthenticationBypass(boolean staticResourceAuthenticationBypass) {
this.staticResourceAuthenticationBypass = staticResourceAuthenticationBypass;
}
Comment thread
Copilot marked this conversation as resolved.

/**
* Pattern the {@link SecurityFilter} should match. Default value `/**`. URLS NOT MATCHED BY PREVIOUS PATTERN ARE NOT SECURED
* @param pattern The pattern
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2017-2026 original authors
*
* 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
*
* https://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 io.micronaut.security.filters;

/**
* Decides whether authentication resolution can be skipped for a request.
*
* @since 5.4.0
* @param <T> The Request
*/
public interface StaticResourceAuthenticationBypass<T> {

/**
* Whether authentication resolution can be skipped for the request.
*
* @param request The current request
* @return Whether authentication resolution can be skipped
* @since 5.4.0
*/
boolean shouldBypass(T request);
}
Comment thread
Copilot marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@
package io.micronaut.security.rules;

import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.Internal;
import io.micronaut.http.HttpRequest;
import io.micronaut.security.config.InterceptUrlMapPattern;
import io.micronaut.security.config.InterceptUrlPatternModifier;
import io.micronaut.security.config.SecurityConfiguration;
import io.micronaut.security.token.RolesFinder;
import jakarta.inject.Singleton;
import java.util.List;
import io.micronaut.http.HttpRequest;

/**
* A security rule implementation backed by the {@link SecurityConfiguration#getInterceptUrlMap()}.
Expand Down Expand Up @@ -66,4 +67,18 @@ protected List<InterceptUrlMapPattern> getPatternList() {
public int getOrder() {
return ORDER;
}

/**
* Whether the first configured pattern that applies to the request allows anonymous access.
*
* @param request The current request
* @return Whether authentication resolution can be skipped for the request
* @since 5.4.0
*/
Comment thread
Copilot marked this conversation as resolved.
@Internal
public boolean isAnonymous(HttpRequest<?> request) {
return findPattern(request)
.map(pattern -> pattern.getAccess().contains(SecurityRule.IS_ANONYMOUS))
.orElse(false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ protected InterceptUrlMapRule(RolesFinder rolesFinder) {
*/
@Override
public Publisher<SecurityRuleResult> check(HttpRequest<?> request, @Nullable Authentication authentication) {
return Mono.from(findPattern(request)
.map(pattern -> compareRoles(pattern.getAccess(), getRoles(authentication)))
.orElse(Mono.just(SecurityRuleResult.UNKNOWN)));
}

/**
* Finds the first configured pattern that applies to the request, using the same
* method-specific precedence as {@link #check(HttpRequest, Authentication)}.
*
* @param request The current request
* @return The matching pattern, if any
*/
protected Optional<InterceptUrlMapPattern> findPattern(HttpRequest<?> request) {
final String path = StringUtils.trimTrailingSlashExceptRoot(request.getUri().getPath());
final HttpMethod httpMethod = request.getMethod();

Expand Down Expand Up @@ -104,8 +117,6 @@ public Publisher<SecurityRuleResult> check(HttpRequest<?> request, @Nullable Aut
}
}

return Mono.from(matchedPattern
.map(pattern -> compareRoles(pattern.getAccess(), getRoles(authentication)))
.orElse(Mono.just(SecurityRuleResult.UNKNOWN)));
return matchedPattern;
}
}
Loading
Loading