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

io.helidon.security.integration.webserver.WebSecurity Maven / Gradle / Ivy

There is a newer version: 4.0.0-ALPHA6
Show newest version
/*
 * Copyright (c) 2018, 2020 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.security.integration.webserver;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import io.helidon.common.http.Http;
import io.helidon.config.Config;
import io.helidon.config.ConfigValue;
import io.helidon.security.EndpointConfig;
import io.helidon.security.Security;
import io.helidon.security.SecurityContext;
import io.helidon.security.SecurityEnvironment;
import io.helidon.webserver.Routing;
import io.helidon.webserver.ServerRequest;
import io.helidon.webserver.ServerResponse;
import io.helidon.webserver.Service;

/**
 * Integration of security into Web Server.
 * 

* Methods that start with "from" are to register WebSecurity with {@link io.helidon.webserver.WebServer} * - to create {@link SecurityContext} for requests: *

    *
  • {@link #create(Security)}
  • *
  • {@link #create(Config)}
  • *
  • {@link #create(Security, Config)}
  • *
*

* Example: *

 * // Web server routing builder - this is our integration point
 * {@link io.helidon.webserver.Routing} routing = Routing.builder()
 * // register the WebSecurity to create context (shared by all routes)
 * .register({@link WebSecurity}.{@link
 * WebSecurity#create(Security) from(security)})
 * 
*

* Other methods are to create security enforcement points (gates) for routes (e.g. you are expected to use them for a get, post * etc. routes on specific path). * These methods are starting points that provide an instance of {@link SecurityHandler} that has finer grained methods to * control the gate behavior.
* Note that if any gate is configured, auditing will be enabled by default except for GET and HEAD methods - if you want * to audit any method, invoke {@link #audit()} to create a gate that will always audit the route. * If you want to create a gate and not audit it, use {@link SecurityHandler#skipAudit()} on the returned instance. *

    *
  • {@link #secure()} - authentication and authorization
  • *
  • {@link #rolesAllowed(String...)} - role based access control (implies authentication and authorization)
  • *
  • {@link #authenticate()} - authentication only
  • *
  • {@link #authorize()} - authorization only
  • *
  • {@link #allowAnonymous()} - authentication optional
  • *
  • {@link #audit()} - audit all requests (including GET and HEAD)
  • *
  • {@link #authenticator(String)} - use explicit authenticator (named - as configured in config or through builder)
  • *
  • {@link #authorizer(String)} - use explicit authorizer (named - as configured in config or through builder)
  • *
  • {@link #enforce()} - use defaults (e.g. no authentication, authorization, audit calls except for GET and HEAD); this * also give access to more fine-grained methods of {@link SecurityHandler}
  • *
*

* Example: *

 * // continue from example above...
 * // create a gate for method GET: authenticate all paths under /user and require role "user" for authorization
 * .get("/user[/{*}]", WebSecurity.{@link WebSecurity#rolesAllowed(String...)
 * rolesAllowed("user")})
 * 
*/ public final class WebSecurity implements Service { /** * Security can accept additional headers to be added to security request. * This will be used to obtain multivalue string map (a map of string to list of strings) from context (appropriate * to the integration). */ public static final String CONTEXT_ADD_HEADERS = "security.addHeaders"; private static final AtomicInteger SECURITY_COUNTER = new AtomicInteger(); private final Security security; private final Config config; private final SecurityHandler defaultHandler; private WebSecurity(Security security, Config config) { this(security, config, SecurityHandler.create()); } private WebSecurity(Security security, Config config, SecurityHandler defaultHandler) { this.security = security; this.config = config; this.defaultHandler = defaultHandler; } /** * Create a consumer of routing config to be {@link Routing.Builder#register(Service...) registered} with * web server routing to process security requests. * This method is to be used together with other routing methods to protect web resources programmatically. * Example: *
     * .get("/user[/{*}]", WebSecurity.authenticate()
     * .rolesAllowed("user"))
     * 
* * @param security initialized security * @return routing config consumer */ public static WebSecurity create(Security security) { return new WebSecurity(security, null); } /** * Create a consumer of routing config to be {@link Routing.Builder#register(Service...) registered} with * web server routing to process security requests. * This method configures security and web server integration from a config instance * * @param config Config instance to load security and web server integration from configuration * @return routing config consumer */ public static WebSecurity create(Config config) { Security security = Security.create(config); return create(security, config); } /** * Create a consumer of routing config to be {@link Routing.Builder#register(Service...) registered} with * web server routing to process security requests. * This method expects initialized security and creates web server integration from a config instance * * @param security Security instance to use * @param config Config instance to load security and web server integration from configuration * @return routing config consumer */ public static WebSecurity create(Security security, Config config) { return new WebSecurity(security, config); } /** * Secure access using authentication and authorization. * Auditing is enabled by default for methods modifying content. * When using RBAC (role based access control), just use {@link #rolesAllowed(String...)}. * If you use a security provider, that requires additional data, use {@link SecurityHandler#customObject(Object)}. *

* Behavior: *

    *
  • Authentication: enabled and required
  • *
  • Authorization: enabled if provider configured
  • *
  • Audit: not modified (default: enabled except for GET and HEAD methods)
  • *
* * @return {@link SecurityHandler} instance configured with authentication and authorization */ public static SecurityHandler secure() { return SecurityHandler.create().authenticate().authorize(); } /** * If called, request will go through authentication process - defaults to false (even if authorize is true). *

* Behavior: *

    *
  • Authentication: enabled and required
  • *
  • Authorization: not modified (default: disabled)
  • *
  • Audit: not modified (default: enabled except for GET and HEAD methods)
  • *
* * @return {@link SecurityHandler} instance */ public static SecurityHandler authenticate() { return SecurityHandler.create().authenticate(); } /** * Whether to audit this request - defaults to false for GET and HEAD methods, true otherwise. * Request is audited with event type "request". *

* Behavior: *

    *
  • Authentication: not modified (default: disabled)
  • *
  • Authorization: not modified (default: disabled)
  • *
  • Audit: enabled for any method this gate is registered on
  • *
* * @return {@link SecurityHandler} instance */ public static SecurityHandler audit() { return SecurityHandler.create().audit(); } /** * Use a named authenticator (as supported by security - if not defined, default authenticator is used). *

* Behavior: *

    *
  • Authentication: enabled and required
  • *
  • Authorization: not modified (default: disabled)
  • *
  • Audit: not modified (default: enabled except for GET and HEAD methods)
  • *
* * @param explicitAuthenticator name of authenticator as configured in {@link Security} * @return {@link SecurityHandler} instance */ public static SecurityHandler authenticator(String explicitAuthenticator) { return SecurityHandler.create().authenticate().authenticator(explicitAuthenticator); } /** * Use a named authorizer (as supported by security - if not defined, default authorizer is used, if none defined, all is * permitted). *

* Behavior: *

    *
  • Authentication: enabled and required
  • *
  • Authorization: enabled with explicit provider
  • *
  • Audit: not modified (default: enabled except for GET and HEAD methods)
  • *
* * @param explicitAuthorizer name of authorizer as configured in {@link Security} * @return {@link SecurityHandler} instance */ public static SecurityHandler authorizer(String explicitAuthorizer) { return SecurityHandler.create().authenticate().authorize().authorizer(explicitAuthorizer); } /** * An array of allowed roles for this path - must have a security provider supporting roles. *

* Behavior: *

    *
  • Authentication: enabled and required
  • *
  • Authorization: enabled
  • *
  • Audit: not modified (default: enabled except for GET and HEAD methods)
  • *
* * @param roles if subject is any of these roles, allow access * @return {@link SecurityHandler} instance */ public static SecurityHandler rolesAllowed(String... roles) { return SecurityHandler.create().rolesAllowed(roles); } /** * If called, authentication failure will not abort request and will continue as anonymous (defaults to false). *

* Behavior: *

    *
  • Authentication: enabled and optional
  • *
  • Authorization: not modified (default: disabled)
  • *
  • Audit: not modified (default: enabled except for GET and HEAD methods)
  • *
* * @return {@link SecurityHandler} instance */ public static SecurityHandler allowAnonymous() { return SecurityHandler.create().authenticate().authenticationOptional(); } /** * Enable authorization for this route. *

* Behavior: *

    *
  • Authentication: enabled and required
  • *
  • Authorization: enabled if provider is present
  • *
  • Audit: not modified (default: enabled except for GET and HEAD methods)
  • *
* * @return {@link SecurityHandler} instance */ public static SecurityHandler authorize() { return SecurityHandler.create().authorize(); } /** * Return a default instance to create a default enforcement point (or modify the result further). *

* Behavior: *

    *
  • Authentication: not modified (default: disabled)
  • *
  • Authorization: not modified (default: disabled)
  • *
  • Audit: not modified (default: enabled except for GET and HEAD methods)
  • *
* * @return {@link SecurityHandler} instance */ public static SecurityHandler enforce() { return SecurityHandler.create(); } /** * Create a new web security instance using the default handler as base defaults for all handlers used. * If handlers are loaded from config, than this is the least significant value. * * @param defaultHandler if a security handler is configured for a route, it will take its defaults from this handler * @return new instance of web security with the handler default */ public WebSecurity securityDefaults(SecurityHandler defaultHandler) { Objects.requireNonNull(defaultHandler, "Default security handler must not be null"); return new WebSecurity(security, config, defaultHandler); } @Override public void update(Routing.Rules routing) { routing.any(this::registerContext); if (null != config) { // only configure routing if we were asked to do so (otherwise it must be configured by hand on web server) registerRouting(routing); } } private void registerContext(ServerRequest req, ServerResponse res) { Map> allHeaders = new HashMap<>(req.headers().toMap()); Optional newHeaders = req.context().get(CONTEXT_ADD_HEADERS, Map.class); newHeaders.ifPresent(allHeaders::putAll); //make sure there is no context if (!req.context().get(SecurityContext.class).isPresent()) { SecurityEnvironment env = security.environmentBuilder() .targetUri(req.uri()) .path(req.path().toString()) .method(req.method().name()) .addAttribute("userIp", req.remoteAddress()) .addAttribute("userPort", req.remotePort()) .transport(req.isSecure() ? "https" : "http") .headers(allHeaders) .build(); EndpointConfig ec = EndpointConfig.builder() .build(); SecurityContext.Builder contextBuilder = security.contextBuilder(String.valueOf(SECURITY_COUNTER.incrementAndGet())) .env(env) .endpointConfig(ec); // only register if exists req.spanContext().ifPresent(contextBuilder::tracingSpan); SecurityContext context = contextBuilder.build(); req.context().register(context); req.context().register(defaultHandler); } req.next(); } private void registerRouting(Routing.Rules routing) { Config wsConfig = config.get("web-server"); SecurityHandler defaults = SecurityHandler.create(wsConfig.get("defaults"), defaultHandler); wsConfig.get("paths").asNodeList().ifPresent(configs -> { for (Config pathConfig : configs) { List methods = pathConfig.get("methods").asNodeList().orElse(List.of()) .stream() .map(Config::asString) .map(ConfigValue::get) .map(Http.RequestMethod::create) .collect(Collectors.toList()); String path = pathConfig.get("path") .asString() .orElseThrow(() -> new SecurityException(pathConfig .key() + " must contain path key with a path to " + "register to web server")); if (methods.isEmpty()) { routing.any(path, SecurityHandler.create(pathConfig, defaults)); } else { routing.anyOf(methods, path, SecurityHandler.create(pathConfig, defaults)); } } }); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy