All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.microprofile.security.JerseySecurityFeature Maven / Gradle / Ivy

/*
 * Copyright (c) 2018, 2023 Oracle and/or its affiliates.
 *
 * 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 io.helidon.microprofile.security;

import java.util.LinkedList;
import java.util.List;

import io.helidon.common.config.Config;
import io.helidon.security.Security;
import io.helidon.security.SecurityContext;
import io.helidon.security.annotations.Authorized;

import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.ws.rs.ConstrainedTo;
import jakarta.ws.rs.RuntimeType;
import jakarta.ws.rs.core.Feature;
import jakarta.ws.rs.core.FeatureContext;
import jakarta.ws.rs.core.GenericType;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.ReferencingFactory;
import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.process.internal.RequestScoped;

import static java.lang.System.Logger.Level.TRACE;

/**
 * Integration of Security module with Jersey.
 * 

* Register this as you would any other feature, e.g.: *

 * ResourceConfig resourceConfig = new ResourceConfig();
 * // register JAX-RS resource
 * resourceConfig.register(MyResource.class);
 * // integrate security
 * resourceConfig.register(new SecurityFeature(buildSecurity()));
 * 
*/ @ConstrainedTo(RuntimeType.SERVER) public final class JerseySecurityFeature implements Feature { private static final System.Logger LOGGER = System.getLogger(JerseySecurityFeature.class.getName()); private final Security security; private final FeatureConfig featureConfig; /** * Create a new instance of security feature for a security component. *
* * This constructor is workaround solution for Jersey instantiation problem. */ public JerseySecurityFeature() { this.security = null; this.featureConfig = null; } /** * Create a new instance of security feature for a security component. * * @param security Fully configured security component to integrate with Jersey */ public JerseySecurityFeature(Security security) { this.security = security; this.featureConfig = new FeatureConfig(builder(security).config(security.configFor("jersey"))); } private JerseySecurityFeature(Builder builder) { this.security = builder.security; this.featureConfig = new FeatureConfig(builder); } /** * Builder for {@link JerseySecurityFeature}. * * @param security Security instance to create this feature for (cannot build a feature without security instance) * @return Builder to configure feature */ public static Builder builder(Security security) { return new Builder(security); } @Override public boolean configure(FeatureContext context) { RuntimeType runtimeType = context.getConfiguration().getRuntimeType(); //register server if (runtimeType != RuntimeType.SERVER) { return false; } if (LOGGER.isLoggable(TRACE)) { LOGGER.log(TRACE, "Configuring Security feature in Jersey server runtime"); } context.register(SecurityPreMatchingFilter.class); context.register(SecurityFilter.class); //allow injection of security context (our, not Jersey) context.register(new AbstractBinder() { @Override protected void configure() { bindFactory(SecurityContextRefFactory.class) .to(SecurityContext.class) .proxy(true) .proxyForSameScope(false) .in(RequestScoped.class); bindFactory(ReferencingFactory.referenceFactory()) .to(new GenericType>() { }) .in(RequestScoped.class); bind(security).to(Security.class); bind(featureConfig).to(FeatureConfig.class); } }); return true; } FeatureConfig featureConfig() { return featureConfig; } /** * {@link JerseySecurityFeature} fluent API builder. */ @SuppressWarnings("UnusedReturnValue") public static final class Builder implements io.helidon.common.Builder { private final Security security; private final List queryParamHandlers = new LinkedList<>(); private boolean authorizeAnnotatedOnly = FeatureConfig.DEFAULT_ATZ_ANNOTATED_ONLY; private boolean authenticateAnnotatedOnly = FeatureConfig.DEFAULT_ATN_ANNOTATED_ONLY; private boolean debug = FeatureConfig.DEFAULT_DEBUG; private boolean prematchingAuthorization = FeatureConfig.DEFAULT_PREMATCHING_ATZ; private boolean prematchingAuthentication = FeatureConfig.DEFAULT_PREMATCHING_ATN; private boolean useAbortWith = FeatureConfig.DEFAULT_USE_ABORT_WITH; private boolean failOnFailureIfOptional = FeatureConfig.DEFAULT_ATN_FAIL_ON_FAILURE_IF_OPT; private Builder(Security security) { this.security = security; } /** * Whether to authorize only annotated methods (with {@link Authorized} annotation) or all. * When using {@link #usePrematchingAuthorization(boolean)} * this method is ignored. * * @param authzOnly if set to true, authorization will be performed on annotated methods only, defaults to false * @return updated builder instance */ public Builder authorizeAnnotatedOnly(boolean authzOnly) { this.authorizeAnnotatedOnly = authzOnly; return this; } /** * Whether to authorize only annotated methods (with {@link io.helidon.security.annotations.Authenticated} annotation or all. * When using {@link #usePrematchingAuthentication(boolean)} this method is ignored. * By default only annotated methods (annotation may be also on Application class or resource class) are authenticated. * * @param authnOnly if set to false, authentication will be performed for all requests, defaults to true * @return updated builder instance */ public Builder authenticateAnnotatedOnly(boolean authnOnly) { this.authenticateAnnotatedOnly = authnOnly; return this; } /** * Add a new handler to extract query parameter and store it in security request header. * * @param handler handler to extract data * @return updated builder instance */ public Builder addQueryParamHandler(QueryParamHandler handler) { this.queryParamHandlers.add(handler); return this; } /** * Add handlers to extract query parameters and store them in security request header. * * @param handlers handlers to extract data * @return updated builder instance */ public Builder addQueryParamHandlers(Iterable handlers) { handlers.forEach(this::addQueryParamHandler); return this; } /** * Configure whether pre-matching or post-matching filter is used to authenticate requests. * Defaults to post-matching, as we have access to information about resource class and method that is * invoked, allowing us to use annotations defined on these. * When switched to prematching, the security is an on/off switch - all resources are protected the * same way. * * @param usePrematching whether to use pre-matching filter instead of post-matching * @return updated builder instance */ public Builder usePrematchingAuthentication(boolean usePrematching) { this.prematchingAuthentication = usePrematching; return this; } /** * Configure whether pre-matching or post-matching filter is used to authorize requests. * Defaults to post-matching, as we have access to information about resource class and method that is * invoked, allowing us to use annotations defined on these. * When switched to prematching, the security is an on/off switch - all resources are protected the * same way. *
* * When set to true, authentication will be prematching as well. * * @param usePrematching whether to use pre-matching filter instead of post-matching * @return updated builder instance */ public Builder usePrematchingAuthorization(boolean usePrematching) { this.prematchingAuthorization = usePrematching; return this; } /** * Whether to fail in case of authentication failure if authentication is optional. * When set to {@code true}, authentication will fail in case of failure even when it is set * as an optional. *
* * Default value is {@code false}. * * @param failOnFailureIfOptional fail on authentication failure if optional * @return updated builder instance */ private Builder failOnFailureIfOptional(Boolean failOnFailureIfOptional) { this.failOnFailureIfOptional = failOnFailureIfOptional; return this; } /** * Set debugging on. * Will return description from response in entity. * * @return updated builder instance */ public Builder debug() { this.debug = true; return this; } /** * When set to {@code true} (which is the default behavior, the security filter * would use {@link org.glassfish.jersey.server.ContainerRequest#abortWith(jakarta.ws.rs.core.Response)} to * abort request and configure a security response. *

* When set to {@code false}, the security filter would throw an {@link jakarta.ws.rs.WebApplicationException} instead. * Such an exception can be handled by a custom error handler. * * @param useAbortWith set to {@code false} to use exceptions, by default uses abortWith on request * @return updated builder instance */ public Builder useAbortWith(boolean useAbortWith) { this.useAbortWith = useAbortWith; return this; } /** * Update this builder from configuration. * Expects: *

    *
  • authorize-annotated-only: see {@link #authorizeAnnotatedOnly(boolean)}
  • *
  • query-params: see {@link #addQueryParamHandler(QueryParamHandler)}
  • *
* Example: *
         *  security:
         *    jersey:
         *      defaults:
         *      # If set to true, only annotated (@Authenticated) resources will be authorized
         *      # By default, every request is sent to authorization provider
         *      authorize-annotated-only: false
         *      # query parameters will be extracted from request
         *      # and sent to authentication and authorization providers
         *      # as headers. These will NOT be available to application
         *      # as headers.
         *      query-params:
         *        - name: "basicAuth"
         *          header: "Authorization"
         * 
* * @param config configuration set to key "jersey" (see example above) * @return updated builder instance */ public Builder config(Config config) { config.get("prematching-authentication").asBoolean().ifPresent(this::usePrematchingAuthentication); config.get("prematching-authorization").asBoolean().ifPresent(this::usePrematchingAuthorization); config.get("use-abort-with").asBoolean().ifPresent(this::useAbortWith); config.get("fail-on-failure-if-optional").asBoolean().ifPresent(this::failOnFailureIfOptional); Config myConfig = config.get("defaults"); myConfig.get("authorize-annotated-only").asBoolean().ifPresent(this::authorizeAnnotatedOnly); myConfig.get("authenticate-annotated-only").asBoolean().ifPresent(this::authenticateAnnotatedOnly); myConfig.get("query-params").mapList(QueryParamHandler::create).ifPresent(this::addQueryParamHandlers); myConfig.get("debug").asBoolean().filter(bool -> bool).ifPresent(bool -> this.debug()); return this; } /** * Build this configuration into an instance. * * @return feature to register with Jersey */ @Override public JerseySecurityFeature build() { return new JerseySecurityFeature(this); } Security security() { return security; } List queryParamHandlers() { return queryParamHandlers; } boolean isAuthorizeAnnotatedOnly() { return authorizeAnnotatedOnly; } boolean isAuthenticateAnnotatedOnly() { return authenticateAnnotatedOnly; } boolean isDebug() { return debug; } boolean isPrematchingAuthorization() { return prematchingAuthorization; } boolean isPrematchingAuthentication() { return prematchingAuthentication; } boolean useAbortWith() { return useAbortWith; } boolean failOnFailureIfOptional() { return failOnFailureIfOptional; } } private static class SecurityContextRefFactory extends ReferencingFactory { @Inject SecurityContextRefFactory(Provider> referenceFactory) { super(referenceFactory); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy