Spring State Machine - Change state based on conditions - spring

I am trying to create a simple state machine as shown below
For this I have below config
#Override
public void configure(final StateMachineStateConfigurer<States, Events> states) throws Exception {
states
.withStates()
.states(EnumSet.allOf(States.class))
.initial(States.NEW)
.end(States.ERROR)
.end(States.DELIVER);
}
and below transitions
#Override
public void configure(final StateMachineTransitionConfigurer<States, Events> transitions)
throws Exception {
transitions
.withExternal()
.source(States.NEW).target(States.PACKAGED).event(Events.pack)
.and()
.withExternal()
.source(States.PACKAGED).target(States.PROCESS).event(Events.process)
.and()
.withExternal()
.source(States.PACKAGED).target(States.ERROR).event(Events.error)
.and()
.withExternal()
.source(States.PROCESS).target(States.DELIVER).event(Events.deliver)
.and()
.withExternal()
.source(States.PROCESS).target(States.ERROR).event(Events.error);
}
I am trying to right a condition such that when packaging or processing the order, if any error is encountered the state of the order should be error state.
I noticed that there are transition action that can be added while configuring the transition as below
public Action<States, Events> packageAction() {
// Packaging logic
if(packed){
return context -> context.getStateMachine().sendEvent(Events.process);
}else{
return context -> context.getStateMachine().sendEvent(Events.error);
}
}
But after running the application it doesn't work.
Is this the right way to publish events conditionally?

Its hard to say without seeing the stacktrace, but one thing I noticed in your configuration is that you cannot have two end states since it breaks a Finite State Machine.
Regarding you question about the transition logic, the right way of doing it is with a choice() pseudo state and guards. Guards are interfaces that should contain logic to allow a choice transition to define which state it should enter. In your case, it would be something like this:
#Override
public void configure(final StateMachineStateConfigurer<> states)
throws
Exception {
.withStates()
.states(EnumSet.allOf(States.class))
.initial(States.NEW)
.choice(States.PROCESS)
.end(States.DELIVER);
}
#Override
public void configure(final StateMachineTransitionConfigurer<> transitions)
throws
Exception {
//Configs
.and()
.withChoice()
.source(States.PROCESS)
//you can replace the guard with a lambda expression
//for multiple options, use .then
.first(States.ERROR, hasErrorGuard)
.last(States.DELIVER, successAction, errorAction)

Related

How to remove AbstractHttpConfigurer from default HttpSecurity

I'm creating an internal lib, and I want to perform some autoconfiguration that involve removing security customizers that are added by default, for example LogoutConfigurer, as if it was not part of the default HttpSecurity prototype bean:
#Bean
public SecurityFilterChain authFilter(HttpSecurity http) throws Exception {
return http
// I want to make this unnecessary by not being part of the (adjusted) HttpSecurity
//.logout().disable()
.authorizeRequests()
.mvcMatchers("/secured").hasRole("ROLE")
.anyRequest().denyAll()
.and()
.build(); // (1)
}
The way to customize security in a cross-cutting way like that seems to be implementing AbstractHttpConfigurer beans, yet those are only triggered as part of the HttpScurity#build() method that generates a SecurityFilterChain as part of the main application security configuration code (marked as (1) above). That is too late, as my bean would need to be undoing the configuration done by another customizer just before it, which is complicated and maybe not possible (removing filters, etc).
The only alternative I found so far seems to be overriding the AbstractHttpConfigurer#setBuilder(B) to manipulate the given builder (the HttpSecurity object) into removing the customizers, given that this method is called right after HttpSecurity is created and before making it accessible as a prototype bean:
public class MyHttpConfigurer extends AbstractHttpConfigurer<MyHttpConfigurer, HttpSecurity> {
#Override
public final void setBuilder(HttpSecurity http) {
super.setBuilder(http);
try {
// Do this and/or whatever else you want to do
http.logout().disable();
} catch (Exception e) {
throw new RuntimeException("It failed", e);
}
}
#Override
public final void configure(final HttpSecurity http) throws Exception {}
}
It works as I want, but that looks unstable, abusing the API, and feels it might break without warning. I also found no way to replace the default HttpSecurity prototype builder as it is not conditional.
Is there a cleaner or documented way to achieve this?
I think that the cleanest approach for you to achieve your functionality would be to provide a BeanPostProcessor.
e.g.
#Configuration(proxyBeanMethods = false)
public class CustomSecurityConfiguration {
private static final String HTTP_SECURITY_DEFAULT_BEAN_NAME = "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity";
#Bean
public static BeanPostProcessor httpSecurityBeanPostProcessor() {
return new BeanPostProcessor() {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof HttpSecurity && HTTP_SECURITY_DEFAULT_BEAN_NAME.equals(beanName)) {
HttpSecurity http = (HttpSecurity) bean;
try {
http.logout().disable();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return bean;
}
};
}
}
This is really similar to the example you've proposed. In any case, I do not thing that it is abusing the API since it allows for accessing the HttpSecurity

Custom Authentication Entrypoint not being called on failed Authentication

I have setup an OAUTH Authorization server that's supposed to allow clients request for tokens. It's also supposed to allow admin users carry out other operations.
In my Web Security Configuration:
#Configuration
#EnableWebSecurity
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
private #Autowired CustomAuthenticationProvider authenticationProvider;
private #Autowired CustomAuthenticationEntryPoint entryPoint;
#Override
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().httpBasic().and().cors().and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(entryPoint)
.defaultAuthenticationEntryPointFor(entryPoint, new AntPathRequestMatcher("/api/v1/**"));
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
}
Ideally, when an admin user tries to call any endpoint under "/api/v1/**", they should be authenticated - and in fact, they are.
The issue now is, when authentication fails, the authentication entry endpoint is ignored. I don't understand why this is.
I even included the "default authentication entry point for" just to see if that would help, but it didn't.
Please, how do I resolve this?
After playing around with the http security configuration, I took inspiration from this article (https://www.baeldung.com/spring-security-basic-authentication) and changed it to:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic().authenticationEntryPoint(entryPoint);
}
Honestly, I don't know why what I had before wasn't working. Plenty of people have posted that as the solution to problems about entry end points. But I guess maybe something has changed in Spring that I'm not aware of.

Spring security: How can I enable anonymous for some matchers, but disable that for the rest?

I am trying to enable the anonymous access to some part of my rest api, but disable that to the rest.
I tried config looks like:
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anonymous().and()
.antMatchers(SOME_URL).authenticated()
.and()
.anoymous().disable()
.antMatchers(OTHER_URL).authenticated();
}
But later, I realized that the later anonymous().disable will cover the previous setting.
So is anyone can give me some suggestion that how can I enable the anonymous for part of my url?
Many thanks!!!
You can define a RequestMatcher, one for public urls and other for protected urls. Then, override the configure method which accepts WebSecurity as param. In this method, you can configure web to ignore your public urls.
private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/public/**")
);
private static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
#Override
public void configure(final WebSecurity web) {
web.ignoring().requestMatchers(PUBLIC_URLS);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.exceptionHandling()
// this entry point handles when you request a protected page and you are not yet
// authenticated
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.anyRequest()
.authenticated();
// and other clauses you would like to add.
}

Spring Security Java Config same url allowed for anonymous user and for others authentication needed

In Spring Security Java Config
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/guest/**").authenticated;
}
What if I want this same url to be allowed access to a particular principal or a User.
And others Authentication needed. Is it possible?
If you want to completely bypass any security checks for certain URLs, you could do the following:
#Override
public void configure(WebSecurity web) throws Exception {
// configuring here URLs for which security filters
// will be disabled (this is equivalent to using
// security="none")
web
.ignoring()
.antMatchers(
"/guest/**"
)
;
}
This is equivalent to the following XML snippet:
<sec:http security="none" pattern="/guest/**" />
Two approaches; first, use HttpSecurity#not() like this to block anonymous users;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/guest/**")
.not().hasRole("ANONYMOUS");
// more config
}
Or use something like ROLE_VIEW_GUEST_PAGES that gets added depending on the user type from your UserDetailsService. This, IMO gives you better control over who sees guest pages.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/guest/**")
.hasRole("VIEW_GUEST_PAGES");
// more config
}

How to implement Spring Security Ldap authentication using the configurer class correctly?

Hi I'm trying to implement spring's ldap authentication using the WebSecurityConfigurerAdapter class.
So far I can authenticate through the in memory method and even my corp's ldap server, however the latter method I'm only able to authenticate if I pass a hardcoded userDN and password when I create the new context, if I don't create a new context or I don't put the userDN and password, jvm throws me:
Caused by: javax.naming.NamingException: [LDAP: error code 1 - 000004DC: LdapErr: DSID-0C0906E8, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, v1db1\u0000]; Remaining name: '/'
My question is, how can I get the user password and userDN from the login form so I can put it in the context? If that is not possible how can I get the context that the password and userDn are?
This is the code that I have:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication().userSearchFilter("(&(objectClass=user)(sAMAccountName={0}))")
.groupSearchFilter("(&(memberOf:1.2.840.113556.1.4.1941:=CN=DL - DC859 - MIDDLEWARE,OU=Dyn,OU=Dist,OU=Security Groups,OU=POP,DC=pop,DC=corp,DC=local))")
.contextSource(getLdapContextSource());
}
private LdapContextSource getLdapContextSource() throws Exception {
LdapContextSource cs = new LdapContextSource();
cs.setUrl("ldap://tcp-prd.pop.corp.local:389");
cs.setBase("DC=pop,DC=corp,DC=local");
cs.setUserDn("t8951435#pop.corp.local");
cs.setPassword("mypassword");
cs.afterPropertiesSet();
return cs;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
}
Thank you.
I've finally figured it out from this post. I still don't know how to set the group filters, but at least now I can bind to the server.
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("pop.corp.local",
"ldap://tcp-prd.pop.corp.local:389");
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
#Bean
public LoggerListener loggerListener() {
return new LoggerListener();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
EDIT: I finally found out how to filter by groups. It turns out that they added a setSearchFilter() method in ActiveDirectoryLdapAuthenticationProvider class v3.2.6. As I am using an older version I never knew about this. So I made a copy of the class with the method and just created a buildFilter method to create the filter string that is passed to the setSearchFilter.

Resources