Security features are build atop of functionality from a Spring Security. Security features are handy when it is required to protect part of a state machine execution and interaction with it.
Important | |
---|---|
We expect user to be fairly familiar with a Spring Security meaning we don’t go into details of how overall security framework works. For this read Spring Security reference documentation. |
First level of defence with a security is naturally protecting events which really are a driver from user point of view what is going to happen in a state machine. More fine grained security settings can then be defined for transitions and actions. This can be think of like allowing an employee to access a building, walk around it and then giving more detailed access rights to enter different rooms and allow to switch lights on and off while being on those rooms. If you trust your users then event security may be all you need, if you don’t, then more detailed security needs to be applied.
More detailed info can be found from section Section 23.6, “Understanding Security”.
Tip | |
---|---|
For complete example, see sample Chapter 43, Security. |
All generic configurations for security are done in
SecurityConfigurer
which is obtained from
StateMachineConfigurationConfigurer
. Security is disabled on
default even if Spring Security classes are
present.
@Configuration @EnableStateMachine static class Config4 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception { config .withSecurity() .enabled(true) .transitionAccessDecisionManager(null) .eventAccessDecisionManager(null); } }
If absolutely needed AccessDecisionManager
for both events and
transitions can be customised. If decision managers are not defined or
are set to null
, default managers are created internally.
Event security is defined on a global level within a
SecurityConfigurer
.
@Configuration @EnableStateMachine static class Config1 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception { config .withSecurity() .enabled(true) .event("true") .event("ROLE_ANONYMOUS", ComparisonType.ANY); } }
In above configuration we use expression true
which always evaluates
to TRUE. Using an expression which always evaluates to TRUE
would not make sense in a real application but gives a point that
expression needs to return either TRUE or FALSE. We also defined
attribute ROLE_ANONYMOUS
and ComparisonType
ANY
. Using attributes
and expressions, see section Section 23.5, “Using Security Attributes and Expressions”.
Transition security can be defined globally.
@Configuration @EnableStateMachine static class Config6 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception { config .withSecurity() .enabled(true) .transition("true") .transition("ROLE_ANONYMOUS", ComparisonType.ANY); } }
If security is defined in a transition itself it will override any globally set security.
@Configuration @EnableStateMachine static class Config2 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception { transitions .withExternal() .source("S0") .target("S1") .event("A") .secured("ROLE_ANONYMOUS", ComparisonType.ANY) .secured("hasTarget('S1')"); } }
Using attributes and expressions, see section Section 23.5, “Using Security Attributes and Expressions”.
There are no dedicated security definitions for actions in a state
machine, but it can be accomplished using a global method security
from a Spring Security. This simply needs that an Action
is
defined as a proxied @Bean
and its execute
method annotated with a
@Secured
.
@Configuration @EnableStateMachine static class Config3 extends StateMachineConfigurerAdapter<String, String> { @Override public void configure(StateMachineConfigurationConfigurer<String, String> config) throws Exception { config .withSecurity() .enabled(true); } @Override public void configure(StateMachineStateConfigurer<String, String> states) throws Exception { states .withStates() .initial("S0") .state("S1"); } @Override public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception { transitions .withExternal() .source("S0") .target("S1") .action(securedAction()) .event("A"); } @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) @Bean public Action<String, String> securedAction() { return new Action<String, String>() { @Secured("ROLE_ANONYMOUS") @Override public void execute(StateContext<String, String> context) { } }; } }
Global method security needs to be enabled with a Spring Security which is done with along a lines shown below. See Spring Security reference docs for more details.
@Configuration @EnableGlobalMethodSecurity(securedEnabled = true) public static class Config5 extends WebSecurityConfigurerAdapter { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"); } }
Generally there are two ways to define security properties, firstly using security attributes and secondly using security expressions. Attributes are easier to use but are relatively limited in terms of functionality. Expressions provide more features but are a little bit of harder to use.
On default AccessDecisionManager
instances for events and
transitions both use a RoleVoter
, meaning you can use role attributes
familiar from Spring Security.
For attributes we have 3 different comparison types, ANY
, ALL
and
MAJORITY
which maps into default access decision managers
AffirmativeBased
, UnanimousBased
and ConsensusBased
respectively.
If custom AccessDecisionManager
has been defined, comparison type is
effectively discarded as it’s only used to create a default manager.
Security expressions needs to return either TRUE or FALSE.
The base class for expression root objects is
SecurityExpressionRoot
. This provides some common expressions which
are available in both transition and event security.
Table 23.1. Common built-in expressions
Expression | Description |
---|---|
| Returns |
| Returns |
| Returns |
| Returns |
| Allows direct access to the principal object representing the current user |
| Allows direct access to the current |
| Always evaluates to |
| Always evaluates to |
| Returns |
| Returns |
| Returns |
| Returns |
| Returns |
| Returns |
Event id can be matched by using prefix EVENT_
. For example matching
event A
would match with attribte EVENT_A
.
The base class for expression root object for event is
EventSecurityExpressionRoot
. This provides access to a Message
object which is passed around with eventing.
Table 23.2. Event expressions
Expression | Description |
---|---|
| Returns |
Matching transition sources and targets, use prefixes
TRANSITION_SOURCE_
and TRANSITION_TARGET_
respectively.
The base class for expression root object for transition is
TransitionSecurityExpressionRoot
. This provides access to a
Transition
object which is passed around for transition changes.
Table 23.3. Transition expressions
Expression | Description |
---|---|
| Returns |
| Returns |
This section provides more detailed info how security works within a state machine. Not really something you’d need to know but it is always better to be transparent instead of hiding all the magic what happens behind a scenes.
Note | |
---|---|
Security only makes sense if State Machine is executed in a wallet
garden where user don’t have direct access to the application thus
could modify Spring Security’s |
Integration point for security is done with a
StateMachineInterceptor which is then added automatically into a
state machine if security is enabled. Specific class is a
StateMachineSecurityInterceptor
which intercepts events and
transitions. This interceptor then consults Spring Security’s
AccessDecisionManager
if event can be send or if transition can be
executed. Effectively if decision or vote with a AccessDecisionManager
will result an exception, event or transition is denied.
Due to way how AccessDecisionManager
from Spring Security works, we
need one instance of it per secured object. This is a reason why there
is a different manager for events and transitions. In this case events
and transitions are different class objects we’re securing.
On default for events, voters EventExpressionVoter
, EventVoter
and
RoleVoter
are added into a AccessDecisionManager
.
On default for transitions, voters TransitionExpressionVoter
,
TransitionVoter
and RoleVoter
are added into a AccessDecisionManager
.