blob: a69f54628331aa3c464e8943466ae3e74b0dd79d [file] [log] [blame]
: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`.