inter-feign-client-keycloak-token-filter
diff --git a/library/src/main/java/org/apache/fineract/cn/anubis/config/FinKeycloakSecurityConfigurerAdapter.java b/library/src/main/java/org/apache/fineract/cn/anubis/config/FinKeycloakSecurityConfigurerAdapter.java
index 7860409..20a67e4 100644
--- a/library/src/main/java/org/apache/fineract/cn/anubis/config/FinKeycloakSecurityConfigurerAdapter.java
+++ b/library/src/main/java/org/apache/fineract/cn/anubis/config/FinKeycloakSecurityConfigurerAdapter.java
@@ -20,6 +20,7 @@
 
 import com.fasterxml.jackson.annotation.JsonProperty;
 import org.apache.fineract.cn.anubis.filter.IsisAuthenticatedProcessingFilter;
+import org.apache.fineract.cn.anubis.filter.UserContextFilter;
 import org.apache.fineract.cn.anubis.security.FinKeycloakAuthenticationProvider;
 import org.apache.fineract.cn.anubis.security.UrlPermissionChecker;
 import org.apache.fineract.cn.lang.ApplicationName;
@@ -54,9 +55,12 @@
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
 import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
 
+import javax.annotation.PostConstruct;
 import javax.servlet.Filter;
 import java.util.ArrayList;
 import java.util.List;
@@ -70,90 +74,114 @@
 @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
 @ConditionalOnProperty({"authentication.service.keycloak"})
 public class FinKeycloakSecurityConfigurerAdapter extends KeycloakWebSecurityConfigurerAdapter {
- final private Logger logger;
- final private ApplicationName applicationName;
+    final private Logger logger;
+    final private ApplicationName applicationName;
 
- public FinKeycloakSecurityConfigurerAdapter(final @Qualifier(AnubisConstants.LOGGER_NAME) Logger logger,
-                                             final ApplicationName applicationName) {
-  this.logger = logger;
-  this.applicationName = applicationName;
- }
-
- static class CustomKeycloakAccessToken extends AccessToken {
-  @JsonProperty("roles")
-  protected Set<String> roles;
-
-  public Set<String> getRoles() {
-   return roles;
-  }
-
-  public void setRoles(Set<String> roles) {
-   this.roles = roles;
-  }
- }
-
- @Override
- protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
-  return new KeycloakAuthenticationProvider() {
-
-   @Override
-   public Authentication authenticate(Authentication authentication) throws AuthenticationException {
-    KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
-    List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
-
-    for (String role : ((CustomKeycloakAccessToken)((KeycloakPrincipal<KeycloakSecurityContext>)token.getPrincipal()).getKeycloakSecurityContext().getToken()).getRoles()) {
-     grantedAuthorities.add(new KeycloakRole(role));
+    public FinKeycloakSecurityConfigurerAdapter(final @Qualifier(AnubisConstants.LOGGER_NAME) Logger logger,
+                                                final ApplicationName applicationName) {
+        this.logger = logger;
+        this.applicationName = applicationName;
     }
 
-    return new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), new SimpleAuthorityMapper().mapAuthorities(grantedAuthorities));
-   }
+    @Override
+    protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
+        return new KeycloakAuthenticationProvider() {
 
-  };
- }
+            @Override
+            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+                KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
+                List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
 
- @Autowired
- public void configureGlobal(
-         final AuthenticationManagerBuilder auth,
-         @SuppressWarnings("SpringJavaAutowiringInspection") final FinKeycloakAuthenticationProvider provider)
-         throws Exception {
-  auth.authenticationProvider(provider);
- }
+                for (String role : ((CustomKeycloakAccessToken) ((KeycloakPrincipal<KeycloakSecurityContext>) token.getPrincipal()).getKeycloakSecurityContext().getToken()).getRoles()) {
+                    grantedAuthorities.add(new KeycloakRole(role));
+                }
 
- @Bean
- @Override
- protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
-  return new NullAuthenticatedSessionStrategy();
- }
- @Bean
- public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
-  return new KeycloakSpringBootConfigResolver();
- }
+                return new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), new SimpleAuthorityMapper().mapAuthorities(grantedAuthorities));
+            }
 
- @Bean
- public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
-         KeycloakAuthenticationProcessingFilter filter) {
-  FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
-  registrationBean.setEnabled(false);
-  return registrationBean;
- }
+        };
+    }
 
- @Bean
- public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) {
-  FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
-  registrationBean.setEnabled(false);
-  return registrationBean;
- }
+    @PostConstruct
+    public void configureSecurityContext() {
+        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
+    }
 
- private AccessDecisionManager defaultAccessDecisionManager() {
-  final List<AccessDecisionVoter<?>> voters = new ArrayList<>();
-  voters.add(new UrlPermissionChecker(logger, applicationName));return new UnanimousBased(voters);
- }
+    @Bean
+    public FilterRegistrationBean securityFilterChain(@Qualifier(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) final Filter securityFilter) {
+        final FilterRegistrationBean registration = new FilterRegistrationBean(securityFilter);
+        registration.setOrder(Integer.MIN_VALUE + 1); //Just after the tenant filter.
+        registration.setName(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
+        return registration;
+    }
 
- protected void configure(HttpSecurity http) throws Exception {
-  Filter filter = new IsisAuthenticatedProcessingFilter(super.authenticationManager());
-  ((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((UrlAuthorizationConfigurer.StandardInterceptUrlRegistry)((UrlAuthorizationConfigurer.AuthorizedUrl)((UrlAuthorizationConfigurer)((HttpSecurity)((HttpSecurity)http.httpBasic().disable()).csrf().disable()).apply(new UrlAuthorizationConfigurer(this.getApplicationContext()))).getRegistry().anyRequest()).hasAuthority("maats_feather").accessDecisionManager(this.defaultAccessDecisionManager())).and()).formLogin().disable()).logout().disable()).addFilter(filter).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()).exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
-   response.setStatus(404);
-  });
- }
+    @Bean
+    public FilterRegistrationBean userContextFilter() {
+        final FilterRegistrationBean registration = new FilterRegistrationBean(new UserContextFilter());
+        registration.setOrder(Integer.MIN_VALUE + 2);
+        registration.addUrlPatterns("*");
 
+        return registration;
+    }
+
+
+    @Autowired
+    public void configureGlobal(
+            final AuthenticationManagerBuilder auth,
+            @SuppressWarnings("SpringJavaAutowiringInspection") final FinKeycloakAuthenticationProvider provider)
+            throws Exception {
+        auth.authenticationProvider(provider);
+    }
+
+    @Bean
+    @Override
+    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
+        return new NullAuthenticatedSessionStrategy();
+    }
+
+    @Bean
+    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
+        return new KeycloakSpringBootConfigResolver();
+    }
+
+    @Bean
+    public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
+            KeycloakAuthenticationProcessingFilter filter) {
+        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
+        registrationBean.setEnabled(false);
+        return registrationBean;
+    }
+
+    @Bean
+    public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) {
+        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
+        registrationBean.setEnabled(false);
+        return registrationBean;
+    }
+
+    private AccessDecisionManager defaultAccessDecisionManager() {
+        final List<AccessDecisionVoter<?>> voters = new ArrayList<>();
+        voters.add(new UrlPermissionChecker(logger, applicationName));
+        return new UnanimousBased(voters);
+    }
+
+    protected void configure(HttpSecurity http) throws Exception {
+        Filter filter = new IsisAuthenticatedProcessingFilter(super.authenticationManager());
+        ((HttpSecurity) ((HttpSecurity) ((HttpSecurity) ((HttpSecurity) ((UrlAuthorizationConfigurer.StandardInterceptUrlRegistry) ((UrlAuthorizationConfigurer.AuthorizedUrl) ((UrlAuthorizationConfigurer) ((HttpSecurity) ((HttpSecurity) http.httpBasic().disable()).csrf().disable()).apply(new UrlAuthorizationConfigurer(this.getApplicationContext()))).getRegistry().anyRequest()).hasAuthority("maats_feather").accessDecisionManager(this.defaultAccessDecisionManager())).and()).formLogin().disable()).logout().disable()).addFilter(filter).sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()).exceptionHandling().accessDeniedHandler((request, response, accessDeniedException) -> {
+            response.setStatus(404);
+        });
+    }
+
+    static class CustomKeycloakAccessToken extends AccessToken {
+        @JsonProperty("roles")
+        protected Set<String> roles;
+
+        public Set<String> getRoles() {
+            return roles;
+        }
+
+        public void setRoles(Set<String> roles) {
+            this.roles = roles;
+        }
+    }
 }
\ No newline at end of file
diff --git a/library/src/main/java/org/apache/fineract/cn/anubis/security/AccountLevelAccessDeniedException.java b/library/src/main/java/org/apache/fineract/cn/anubis/security/AccountLevelAccessDeniedException.java
new file mode 100644
index 0000000..d645fdd
--- /dev/null
+++ b/library/src/main/java/org/apache/fineract/cn/anubis/security/AccountLevelAccessDeniedException.java
@@ -0,0 +1,14 @@
+package org.apache.fineract.cn.anubis.security;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * @author manoj
+ */
+public class AccountLevelAccessDeniedException extends AuthenticationException {
+    private AccountLevelAccessDeniedException(final String message) { super(message); }
+
+    public static AccountLevelAccessDeniedException internalError(final String message) {
+        return new AccountLevelAccessDeniedException(message);
+    }
+}
diff --git a/library/src/main/java/org/apache/fineract/cn/anubis/security/AccountLevelAccessVerifierCustom.java b/library/src/main/java/org/apache/fineract/cn/anubis/security/AccountLevelAccessVerifierCustom.java
index 5e66118..602f00a 100644
--- a/library/src/main/java/org/apache/fineract/cn/anubis/security/AccountLevelAccessVerifierCustom.java
+++ b/library/src/main/java/org/apache/fineract/cn/anubis/security/AccountLevelAccessVerifierCustom.java
@@ -49,7 +49,7 @@
                 .collect(Collectors.toSet());
 
         if(accountOperation.size() == 0  || !(accountOperation.contains(OWNER) || accountOperation.contains(operation))) {
-            throw AmitAuthenticationException.internalError("Access Denied, " + operation + " on " + accountNo);
+            throw AccountLevelAccessDeniedException.internalError("Access Denied, " + operation + " on " + accountNo);
         }
     }
 }