| :moduledeps: core |
| |
| = Security Module |
| |
| :Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you 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. |
| |
| == Overview |
| |
| The Security module provides APIs for authorization of method invocations. |
| |
| There are two different APIs provided for two different approaches -- one simple interceptor-style API and another for more complex scenarios. |
| |
| * *<<_simple_interceptor_style_authorization, Simple interceptor-style API>>:* the method that is to be secured is loosely coupled to a predicate method |
| (called _authorizer_ method) which decides whether the secured method invocation should proceed. Similarly to CDI |
| interceptors, the secured method and the authorizer are tied together using a binding annotation -- |
| `@SecurityBindingType` in this case. |
| |
| * *<<_advanced_authorization, Advanced API>>:* this API offers fine-grained control over the authorization process. Multiple independent _voters_ can participate in making the authorization decision and possibly return _security violations_ and thus prevent the method invocation. The voters share a common context. This API is suitable for integration with third-party security frameworks. Also, this API can be used to <<jsf.adoc#_security_integration_via_secured, secure JSF view access>> when using the DeltaSpike JSF module. |
| |
| == Project Setup |
| The configuration information provided here is for Maven-based projects and it assumes that you have already declared the DeltaSpike version and DeltaSpike Core module for your projects, as detailed in <<configure#, Configure DeltaSpike in Your Projects>>. For Maven-independent projects, see <<configure#config-maven-indep,Configure DeltaSpike in Maven-independent Projects>>. |
| |
| === 1. Declare Security Module Dependencies |
| Add the Security module to the list of dependencies in the project `pom.xml` file using this code snippet: |
| |
| [source,xml] |
| ---- |
| <dependency> |
| <groupId>org.apache.deltaspike.modules</groupId> |
| <artifactId>deltaspike-security-module-api</artifactId> |
| <version>${deltaspike.version}</version> |
| <scope>compile</scope> |
| </dependency> |
| |
| <dependency> |
| <groupId>org.apache.deltaspike.modules</groupId> |
| <artifactId>deltaspike-security-module-impl</artifactId> |
| <version>${deltaspike.version}</version> |
| <scope>runtime</scope> |
| </dependency> |
| ---- |
| |
| Or if you're using Gradle, add these dependencies to your `build.gradle`: |
| |
| [source] |
| ---- |
| runtime 'org.apache.deltaspike.modules:deltaspike-security-module-impl' |
| compile 'org.apache.deltaspike.modules:deltaspike-security-module-api' |
| ---- |
| |
| === 2. Enable the SecurityInterceptor |
| For CDI 1.0 (or DeltaSpike v1.1.0 and earlier together with CDI 1.1+), you must enable the security interceptor in the project `beans.xml` file: |
| |
| [source,xml] |
| ---- |
| <beans> |
| <!-- Not needed with CDI 1.1+ and DeltaSpike v1.1.1+ --> |
| <interceptors> |
| <class>org.apache.deltaspike.security.impl.extension.SecurityInterceptor</class> |
| </interceptors> |
| </beans> |
| ---- |
| |
| == Simple interceptor-style authorization |
| This feature of the Security module intercepts method calls and performs a security check before invocation is allowed to proceed. |
| |
| The first piece of code required to use this API is a _security binding_ annotation. This is what we will use to add security behavior to our business classes and methods. |
| |
| .Create the security binding annotation |
| [source,java] |
| ---- |
| @Retention(value = RUNTIME) |
| @Target({TYPE, METHOD}) |
| @Documented |
| @SecurityBindingType |
| public @interface UserLoggedIn {} |
| ---- |
| |
| Next, we must define an _authorizer_ class to implement behavior for our |
| custom security binding type. This class is simply a CDI bean which |
| declares a method annotated `@Secures`, qualified with the security binding |
| annotation we created in the first step. |
| |
| This method has access to the `InvocationContext` of the method call, so |
| if we need to access parameter arguments, we can do so using the given |
| context. Note that we may also inject other beans into the parameter |
| list of our authorizer method. |
| |
| .Create the authorizer |
| [source,java] |
| --------------------------------------------------------------------------------------------------------------------------------- |
| @ApplicationScoped |
| public class LoggedInAuthorizer |
| { |
| @Secures |
| @UserLoggedIn |
| public boolean doSecuredCheck(InvocationContext invocationContext, BeanManager manager, Identity identity) throws Exception |
| { |
| return identity.isLoggedIn(); // perform security check |
| } |
| } |
| --------------------------------------------------------------------------------------------------------------------------------- |
| |
| We can then use our new annotation to secure business or bean methods. |
| This binding annotation may be placed on the entire class (securing all |
| methods) or on individual methods that you wish to secure. |
| |
| .Secure a bean method |
| [source,java] |
| ---------------------------------------- |
| @ApplicationScoped |
| public class SecuredBean1 |
| { |
| @UserLoggedIn |
| public void doSomething(Thing thing) |
| { |
| thing.doSomething(); |
| } |
| } |
| ---------------------------------------- |
| |
| Next, we may access parameter values from the method invocation directly |
| in our authorizer bean by creating custom `@SecurityParameterBinding` |
| types; this is a simple step once we have completed the work above: |
| |
| .Create a parameter binding annotation |
| [source,java] |
| -------------------------------- |
| @Retention(value = RUNTIME) |
| @Target({PARAMETER}) |
| @Documented |
| @SecurityParameterBinding |
| public @interface CurrentThing { |
| } |
| -------------------------------- |
| |
| Now, when a secured method is invoked, we can inject actual parameter |
| values as arguments into our authorizer method, providing domain-level |
| security in our applications: |
| |
| .Update the authorizer to use parameter binding |
| [source,java] |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| @ApplicationScoped |
| public class CustomAuthorizer |
| { |
| @Secures |
| @UserLoggedIn |
| public boolean doSecuredCheck(InvocationContext invocationContext, BeanManager manager, Identity identity, @CurrentThing Thing thing) throws Exception |
| { |
| return thing.hasMember(identity); // perform security check against our method parameter |
| } |
| } |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| |
| Note that our business method must also be annotated. |
| |
| .Complete the Parameter Binding |
| [source,java] |
| ------------------------------------------------------ |
| @ApplicationScoped |
| public class SecuredBean1 |
| { |
| @UserLoggedIn |
| public void doSomething(@CurrentThing Thing thing) |
| { |
| thing.doSomething(); |
| } |
| } |
| ------------------------------------------------------ |
| |
| Our method is now secured, and we are able to use given parameter values |
| as part of our security authorizer! |
| |
| There may be cases where you may want to base your authorization logic |
| on the result of the secured method and do the security check after the |
| method invocation. Just use the same security binding type for that |
| case: |
| |
| [source,java] |
| ---------------------------------- |
| @ApplicationScoped |
| public class SecuredBean1 |
| { |
| @UserLoggedIn |
| public Thing loadSomething() |
| { |
| return thingLoader.load(); |
| } |
| } |
| ---------------------------------- |
| |
| Now you need to access the return value in the authorizer method. You |
| can inject it using the `@SecuredReturn` annotation. Update the authorizer |
| to use a secured return value: |
| |
| [source,java] |
| --------------------------------------------------------------------------------------------------- |
| @ApplicationScoped |
| public class CustomAuthorizer |
| { |
| @Secures |
| @UserLoggedIn |
| public boolean doSecuredCheck(@SecuredReturn Thing thing, Identity identity) throws Exception |
| { |
| return thing.hasMember(identity); // perform security check against the return value |
| } |
| --------------------------------------------------------------------------------------------------- |
| |
| Now the authorization will take place after the method invocation using |
| the return value of the business method. |
| |
| == Advanced authorization |
| |
| This is an alternative to the simple annotation-based interceptor-style API. This API uses the annotation `@Secured` and is mainly a hook for integration of custom security concepts and third-party frameworks. The DeltaSpike Security module is _not_ a full application security solution, but some of the other DeltaSpike modules are security-enabled and use this API (e.g. correct behaviour within custom scope implementations,...). Internally, this `@Secured` API uses the `@Secures`/`@SecurityBindingType` API. |
| |
| (In MyFaces CODI it was originally a CDI interceptor. This part changed |
| a bit, because between the interceptor and `@Secured` is the |
| `@SecurityBindingType` concept which triggers `@Secured` as on possible |
| approach. Therefore the basic behaviour remains the same and you can |
| think about it like an interceptor.) |
| |
| The entry point to this API is the `@Secured` annotation placed either on the whole class -- enabling security for all methods -- or on individual methods. The only other prerequisite is at least one `AccessDecisionVoter` implementation, explained in the next section. |
| |
| .Securing All Intercepted Methods of a CDI Bean |
| [source,java] |
| ----------------------------------------- |
| //... |
| @Secured(CustomAccessDecisionVoter.class) |
| public class SecuredBean |
| { |
| //... |
| } |
| ----------------------------------------- |
| |
| .Securing Specific Methods |
| [source,java] |
| --------------------------------------------- |
| //... |
| public class SecuredBean |
| { |
| @Secured(CustomAccessDecisionVoter.class) |
| public String getResult() |
| { |
| //... |
| } |
| } |
| --------------------------------------------- |
| |
| === AccessDecisionVoter |
| |
| This interface is (besides the `@Secured` annotation) the most important |
| part of the concept. Both artifact types are also the only required |
| parts: |
| |
| [source,java] |
| -------------------------------------------------------------------------------------------------------- |
| public class CustomAccessDecisionVoter implements AccessDecisionVoter |
| { |
| @Override |
| public Set<SecurityViolation> checkPermission(AccessDecisionVoterContext accessDecisionVoterContext) |
| { |
| Method method = accessDecisionVoterContext.<InvocationContext>getSource().getMethod(); |
| |
| //... |
| } |
| } |
| -------------------------------------------------------------------------------------------------------- |
| |
| //// |
| [TODO] tip about the changed parameter/s |
| //// |
| |
| === SecurityViolation |
| |
| In case of a detected violation a `SecurityViolation` has to be added to |
| the result returned by the `AccessDecisionVoter`. |
| |
| === AbstractAccessDecisionVoter |
| |
| You can also implement the abstract class `AbstractAccessDecisionVoter`. |
| This is a convenience class which allows an easier usage: |
| |
| [source,java] |
| ----------------------------------------------------------------------------------------- |
| public class CustomAccessDecisionVoter extends AbstractAccessDecisionVoter |
| { |
| |
| @Override |
| protected void checkPermission(AccessDecisionVoterContext accessDecisionVoterContext, |
| Set<SecurityViolation> violations) |
| { |
| // check for violations |
| violations.add(newSecurityViolation("access not allowed due to ...")); |
| } |
| } |
| ----------------------------------------------------------------------------------------- |
| |
| |
| === @Secured and stereotypes with custom metadata |
| |
| If there are multiple `AccessDecisionVoter` and maybe in different |
| constellations, it is easier to provide an expressive CDI stereotypes for |
| it. Later on that also allows to change the behaviour in a central |
| place. |
| |
| .Stereotype Support of @Secured |
| [source,java] |
| ------------------------------------------- |
| @Named |
| @Admin |
| public class MyBean implements Serializable |
| { |
| //... |
| } |
| |
| //... |
| @Stereotype |
| @Secured(RoleAccessDecisionVoter.class) |
| public @interface Admin |
| { |
| } |
| ------------------------------------------- |
| |
| Furthermore, it is possible to provide custom metadata easily. |
| |
| .Stereotype of @Secured with custom metadata |
| [source,java] |
| ------------------------------------------------------------------------------------------ |
| @Named |
| @Admin(securityLevel=3) |
| public class MyBean implements Serializable |
| { |
| //... |
| } |
| |
| //... |
| @Stereotype |
| @Secured(RoleAccessDecisionVoter.class) |
| public @interface Admin |
| { |
| int securityLevel(); |
| } |
| |
| @ApplicationScoped |
| public class RoleAccessDecisionVoter implements AccessDecisionVoter |
| { |
| private static final long serialVersionUID = -8007511215776345835L; |
| |
| public Set<SecurityViolation> checkPermission(AccessDecisionVoterContext voterContext) |
| { |
| Admin admin = voterContext.getMetaDataFor(Admin.class.getName(), Admin.class); |
| int level = admin.securityLevel(); |
| //... |
| } |
| } |
| ------------------------------------------------------------------------------------------ |
| |
| === AccessDecisionVoterContext |
| |
| Because the `AccessDecisionVoter` can be chained, |
| `AccessDecisionVoterContext` allows to get the current state as well as |
| the results of the security check. |
| |
| There are several methods that can be useful |
| |
| * `getState()` - Exposes the current state : INITIAL, VOTE_IN_PROGRESS, VIOLATION_FOUND, NO_VIOLATION_FOUND |
| * `getViolations()` - Exposes the found violations |
| * `getSource()` - Exposes, for example, the current instance of `javax.interceptor.InvocationContext` in combination with `@Secured` used as interceptor. |
| * `getMetaData()` - Exposes the found meta-data, for example the view-config-class if `@Secured` is used in combination with type-safe view-configs |
| * `getMetaDataFor(String, Class<T>)` - Exposes meta-data for the given key |
| |
| === SecurityStrategy SPI |
| |
| The `SecurityStrategy` interface allows to provide a custom |
| implementation which should be used for `@Secured`. Provide a custom |
| implementation as bean-class in combination with `@Alternative` or |
| `@Specializes` (or as global-alternative). |
| |
| In case of global-alternatives an additional configuration needs to be added to |
| `/META-INF/apache-deltaspike.properties`. |
| |
| .Example |
| ---- |
| globalAlternatives.org.apache.deltaspike.security.spi.authorization.SecurityStrategy=mypackage.CustomSecurityStrategy |
| ---- |
| |
| TIP: The configuration for global alternatives is following the pattern: |
| `globalAlternatives._<interface-name>_=_<implementation-class-name>_` |
| |
| |
| === Examples |
| |
| ==== Redirect to requested page after login |
| DeltaSpike can be combined with pure CDI or with any other security |
| frameworks (like PicketLink) to track the denied page and make it |
| available after user logs in. |
| |
| An example of this use case is available in the examples module in the DeltaSpike repository: |
| |
| * link:https://github.com/apache/deltaspike/tree/master/deltaspike/examples/security-requested-page-after-login-cdi[Making initially requested secured page available for redirect after login with CDI] |
| |
| * link:https://github.com/apache/deltaspike/tree/master/deltaspike/examples/security-requested-page-after-login-picketlink[Making initially requested secured page available for redirect after login with PicketLink] |
| |
| The relevant classes are `AuthenticationListener` and `LoggedInAccessDecisionVoter`. |