Tomcat 7 and AbstractSecurityWebApplicationInitializer are not working together - java

I was following this article to try a basic http-auth without the use of a web.xml
I'm using Tomcat 7.0.41 and those are my dependencies on gradle:
ext.springVersion = "3.2.1.RELEASE"
compile "org.springframework:spring-jdbc:$springVersion",
"org.springframework:spring-context:$springVersion",
"org.springframework:spring-web:$springVersion",
"org.springframework:spring-webmvc:$springVersion",
"org.springframework.security:spring-security-core:3.2.0.M2",
"org.springframework.security:spring-security-web:3.2.0.M2",
"org.springframework.security:spring-security-config:3.2.0.M2",
According to the tutorial I defined the following
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeUrls().antMatchers("/").hasRole("USER")
.and().httpBasic();
}
}
then added that class to the initializer like this:
#Order(1)
public class ServletConfiguration extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { SecurityConfiguration.class };
// return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { AppConfiguration.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
// #Override
// protected Dynamic registerServletFilter(ServletContext servletContext,
// Filter filter) {
// Dynamic securityFilter = servletContext.addFilter(
// "springSecurityFilterChain", DelegatingFilterProxy.class);
// securityFilter.addMappingForUrlPatterns(
// EnumSet.allOf(DispatcherType.class), false, "/*");
// return securityFilter;
// }
}
and finally added a class to inizialize the springSecurityFilterChain:
#Order(2)
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
#Override
protected void afterSpringSecurityFilterChain(ServletContext servletContext) {
System.out.println("afterSpringSecurityFilterChain");
super.afterSpringSecurityFilterChain(servletContext);
}
}
But I'm always getting this error:
DEBUG: org.springframework.jndi.JndiPropertySource - JNDI lookup for name [spring.liveBeansView.mbeanDomain] threw NamingException with message: Name [spring.liveBeansView.mbeanDomain] is not bound in this Context. Unable to find [spring.liveBeansView.mbeanDomain].. Returning null.
Jul 11, 2013 9:22:24 PM org.apache.catalina.core.StandardContext filterStart
SEVERE: Exception starting filter springSecurityFilterChain
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'springSecurityFilterChain' is defined
I don't why though, because when I go debugging the server initialization, actually these two methods are called:
#Override
public final void onStartup(ServletContext servletContext)
throws ServletException {
if(enableHttpSessionEventPublisher()) {
servletContext.addListener(HttpSessionEventPublisher.class);
}
insertSpringSecurityFilterChain(servletContext);
afterSpringSecurityFilterChain(servletContext);
}
then
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = "springSecurityFilterChain";
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
String contextAttribute = getWebApplicationContextAttribute();
if(contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
So actually the filter gets created. But then it gets lost somewhere.
I tried to play with #Order, but that was doing nothing so I tried to register springSecurityFilterChain using the registerServletFilter method but I'm not getting any http-auth request authentication.
And also SecurityConfiguration doens't even get loaded.

SecurityInitializer creates the DelegatingFilterProxy which is used to look up a bean by the name of springSecurityFilterChain. The springSecurityFilterChain is created using #EnableWebSecurity. The problem is that you are missing the #Configuration annotation (without it the Root ApplicationContext is not even going to try to load the SecurityConfiguration). Specifically you want to do the following:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
...
}
A few additional things to point out:
You do not need to use the #Order because you are not adding any other Filters
The only URL you have secured is the context root (i.e. /).
You will want to be aware of a bug with httpBasic() that is discussed on need spring security java config example showing basic auth only
UPDATE: I should have also pointed out that I have already logged SPR-10660 to support #Enable* annotations without having #Configuration on them. After that is resolved, your issue would magically go away.

Related

Dependency Injection into Spring Servlet context (OncePerRequestFilter)

I implemented a OncePerRequestFilter, where in the doFilterInternal() I would like to use an utilization class, that used JdbcTemplate and user data from a properties file. I realized that it couldn't reach the data from the properties file (database connection and variables) and has null value all the time. As I found on the internet it's, because of the different context.
I could successfully setup a new jdbc datasource locally, but I wouldn't like to duplicate the code, so I would like to inject simply the sources the same way as I did everywhere else like in RestControllers (#Value, #Autowired).
Any idea, how could I inject these in my utilization class that will be used in the servlet filter or directly in my filter?
Thank you!
UPDATE - code snippets:
In the RestController, the injection of JdbcTemplate works properly, but in the filter I cannot inject it, always throws nullPointerException.
#SpringBootApplication
public class AsdApplication {
public static void main(String[] args) {
SpringApplication.run(AsdApplication.class, args);
}
public static class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Filter[] getServletFilters() {
DelegatingFilterProxy delegateFilterProxy = new DelegatingFilterProxy();
delegateFilterProxy.setTargetBeanName("MyFilter");
return new Filter[] { delegateFilterProxy };
}
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return null;
}
}
}
#RestController
public class RestCtrl {
#Autowired
private JdbcTemplate jdbcTemplate;
#GetMapping("/test")
public ResponseEntity<String> getTest() {
String result = jdbcTemplate.queryForObject("<query>", String.class);
System.out.println("result in ctrl: " + result);
return new ResponseEntity<>("asd ad asd asd asd", HttpStatus.OK);
}
}
#Component(value = "MyFilter")
public class MyFilter extends OncePerRequestFilter {
#Autowired
private JdbcTemplate jdbcTemplate;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String result = jdbcTemplate.queryForObject("<query>", String.class);
System.out.println("result in filter: " + result);
User currentUser = new User("username", "password", new ArrayList<>());
UsernamePasswordAuthenticationToken authenticatedUser = new UsernamePasswordAuthenticationToken(
currentUser, null, currentUser.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
filterChain.doFilter(request, response);
}
}
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
spring.datasource.url=jdbc:<sqlserver>
spring.datasource.username=<user>
spring.datasource.password=<pass>
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
As you are actually using Spring Boot and want to make it part of the Spring Security filter chain (which is something different!) what you need to do is
Create an #Bean method to create the filter and make it a bean
Create an #Bean method and add a FilterRegistration bean to prevent the bean from being registered as a filter by Spring Boot
Configure Spring Security.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(myFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
#Bean
public MyFilter myFilter() {
return new MyFilter();
}
#Bean
public FilterRegistrationBean<MyFilter> myFilterRegistationBean() {
FilterRegistationBean frb = new FilterRegistrationBean(myFilter());
frb.setEnabled(false);
return frb;
}
Finally remove the #Component from your MyFilter as you don't need it and it would create an additional instance. All prior changes (like the ApplicationInitializer etc. you can remove.
NOTE: As you are using Spring Security and somehow use this for authentication, instead of extending OncePerRequestFilter I suggest you extend the Spring Security AbstractAuthenticationProcessingFilter which integrates better with Spring Security (like fireing events for authentication, logging etc.).
I see you are creating a new instance of MyFilter instead of using the one managed by Spring with #Component(value = "MyFilter")
httpSecurity.addFilterBefore(new MyFilter(), UsernamePasswordAuthenticationFilter.class);
Hence you will hit a NPE since jdbcTemplate is null. You can inject the instance managed be Spring instead of creating a new one.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("MyFilter")
private MyFilter myFilter;
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests().anyRequest().authenticated();
httpSecurity.addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class);
}
}
You should use this:
Through this class you can get different Spring Boot Beans in a non Bean class.
#Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext appContext)
throws BeansException {
ctx = appContext;
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}
Then after creating it, get your bean this way:
ApplicationContext appCtx = ApplicationContextUtils.getApplicationContext();
// Here you get your dependency
ARequiredClass dependency = appCtx.getBean(ARequiredClass.class);

Providing way to configure spring security?

Is it possible to configure Spring security in a way that it reads configuration details from an external file and configures accordingly ?
(I am not talking about changing config at runtime, I am talking about reading from a file at the time of startup).
An example of my existing Spring security config :
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("userPass").roles("USER").build());
manager.createUser(User.withUsername("admin").password("adminPass").roles("ADMIN").build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}
As you can see, I am using multiple configurations (have a look at Order() annotation). What I want to be able to do is decide at the time of startup, the number and types of configuration. For example a first client may want to have 2 configs (e.g.LdapConfig and SamlConfig), a second one may want LdapConfig and SqlConfig and a third one may want 4-5 configs. Is it possible to do that?
NOTE: I am not using Spring Boot
EDIT
Summary of why I want in this way :
By customer I mean the company that will be buying my product. And by users I mean the actual end users of the company that bought my product. So I shipped the product to 3 companies. First will configure it to have ldap auth flow and google-oauth2 auth flow. Users of this first company will be seeing a login page with these 2 options. Company 2 now might have a ldap auth flow and saml auth flow and users of that company will be seeing those 2 options. And the company is selecting the available options before startup.
You could load properties, e.g. DB credentials, before creating your WebApplicationContext. Look at the following example:
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Tell the EnvironmentManager to load the properties. The path to the config
// file is set by Tomcat's home variable. If you change the container you might
// need to change this, too.
EnvironmentParamManager.initialize(System.getProperty("catalina.home"));
// now create the Spring Context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(RootConfig.class);
rootContext.setServletContext(servletContext);
SpringApplicationContextProvider.configure(rootContext);
// ... other config
}
The EnvironmentParamManager could look like this. I've decided to make it static so that the properties are accessible from everywhere even in non-Spring parts of the application.
public class EnvironmentParamManager {
private static Properties properties = new Properties();
public static void initialize(String pathToConfigFile) {
BufferedInputStream stream;
try {
stream = new BufferedInputStream(new FileInputStream(
pathToConfigFile + "myconfig.props"));
properties.load(stream);
stream.close();
} catch (Throwable e) {
throw new Error("Cannot read environment settings from file " + pathToConfigFile);
}
}
public static String getMongoDBHostname() {
return properties.getProperty("mongodb.username");
}
}
When using JavaConfig, you can access your config properties at the Bean creation phase easily like this
#Configuration
public class CoreConfig {
#Bean
public MongoDbFactory mongoDbFactory() throws Exception {
...
ServerAddress address = new
ServerAddress(EnvironmentParamManager.getMongoDBHost(),
EnvironmentParamManager.getMongoDBPort());
...
}
Of course, you are free to connect to any other services like LDAP etc. in just the same way as you load the local properties file before the Spring Context is bootstrapped. Hope that helps.
Selective loading of components can be achived with Springs #Conditional annotation.
The configs would look like this:
#Configuration(value = "some.security.config")
#Conditional(value = LoadSecurityConfigCondition.class)
public class SomeSecurityConfig {
// some code
}
#Configuration(value = "other.security.config")
#Conditional(value = LoadSecurityConfigCondition.class)
public class OtherSecurityConfig {
// other code
}
Then, the LoadSecurityConfigCondition.class decides if the components are loaded:
#Component
public class LoadSecurityConfigCondition implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
boolean enabled = false;
if (metadata.isAnnotated(Configuration.class.getName())) {
final String name = (String) metadata.getAnnotationAttributes(Configuration.class.getName()).get("value");
if (StringUtils.isNotBlank(name)) {
/* Here you may load your config file and
* retrieve the information on wether to load
* the config identified by its name.
*/
enabled = ...;
}
}
return enabled;
}
}
In this example, the config entries can now be created with the #Configuration name, postfixed with .enabled to clarify its purpose:
some.security.config.enabled=true
other.security.config.enabled=false
Have you tried this:
#EnableWebSecurity
#Configuration
public class SecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new MemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password("userPass").roles("USER").build());
manager.createUser(User.withUsername("admin").password("adminPass").roles("ADMIN").build());
return manager;
}
#Configuration
#Profile({"profile1", "profile2"})
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Profile("profile1")
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user").roles("USER");
auth.inMemoryAuthentication().withUser("admin1").password("admin").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/test/**")
.authorizeRequests()
.antMatchers("/api/test/**").authenticated()
.and()
.formLogin();
}
}
}
So with spring.profiles.active=profile1, both configurations are loaded, with spring.profiles.active=profile2, only the first configuration is loaded. Of course, you can use more than 2 profiles, and you can also activate more than one profile at startup (also comma separated). You just need to divide your configurations and profiles in a way that fits your requirements.

Spring 4 REST application using Java configuration (no xml) IllegalArgumentException [duplicate]

This question already has answers here:
java.lang.IllegalArgumentException: Failed to register servlet with name 'dispatcher'.Check if there is another servlet registered under the same name
(2 answers)
Closed 5 years ago.
This is driving me crazy. I've done a good bit of Spring coding but this is the first time that I'm trying to do all configuration in Java. I've searched all over stackoverflow and other places, and while others have seen this problem, none of their solutions have worked for me.
Here are my source files:
Initializer:
public class EnhancedCandidateInfoInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
private static final Logger logger = LoggerFactory.getLogger(EnhancedCandidateInfoInitializer.class);
#Override
protected Class<?>[] getRootConfigClasses() {
logger.info("##### getRootConfigClasses called - returning null #####");
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
logger.info("##### getServletConfigClasses called returning EnhancedCandidateInfoWebConfiguration.class #####");
return new Class[] { EnhancedCandidateInfoWebConfiguration.class };
}
#Override
protected String[] getServletMappings() {
logger.info("##### getServletMappings called #####");
return new String[] { "/" };
}
}
WebMvcConfiguration:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.mojorank.restapi"})
public class EnhancedCandidateInfoWebConfiguration extends WebMvcConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(EnhancedCandidateInfoWebConfiguration.class);
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
logger.info("#### My Configuration handler was called ####");
configurer.enable();
}
}
Controller:
#RestController
public class EnhanceCandidateInfoController {
#RequestMapping("/")
public String welcome() {//Welcome page, non-rest
return "Welcome to RestTemplate Example.";
}
#RequestMapping("/hello/{player}")
public Message message(#PathVariable String player) {//REST Endpoint.
Message msg = new Message(player, "Hello " + player);
return msg;
}
}
When I build and deploy the application to tomcat, I get the following exception stack trace:
Caused by: java.lang.IllegalArgumentException: Failed to register servlet with name 'dispatcher'.Check if there is another servlet registered under the same name.
at org.springframework.util.Assert.notNull(Assert.java:115)
at org.springframework.web.servlet.support.AbstractDispatcherServletInitializer.registerDispatcherServlet(AbstractDispatcherServletInitializer.java:98)
at org.springframework.web.servlet.support.AbstractDispatcherServletInitializer.onStartup(AbstractDispatcherServletInitializer.java:71)
at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:169)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5274)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
As I said, I've searched through stackoverflow and other places and found others that had my same problem, but when I tried to implement the proposed fixes, my problem remained. Thanks in advance for any help.
Change your WebMvcConfiguration method to this:
public class EnhancedCandidateInfoWebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver ivr=new InternalResourceViewResolver();
ivr.setPrefix("/WEB-INF/jsp/");
ivr.setSuffix(".jsp");
ivr.setExposeContextBeansAsAttributes(true);
registry.viewResolver(ivr);
}
}

How to "include" Spring Security configuration into application

I am new to spring security and I was following this example on configuring spring security : https://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/. So I saw that they use this method to let the spring know for the configuration.
public class SpringWebMvcInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { HelloWebSecurityConfiguration.class };
}
...
}
But I have app initialization like this:
public class AppInit implements WebApplicationInitializer{
public void onStartup(ServletContext servletContext) throws ServletException {
// TODO Auto-generated method stub
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfiguration.class);
ServletRegistration.Dynamic registration =
servletContext.addServlet("dispatcher", new DispatcherServlet(context));
registration.setLoadOnStartup(1);
registration.addMapping("/services/rest/*");
}
}
And I want to include my spring security configuration there, as without it I get message in browser: Your login attempt was not successful, try again.
Reason: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Extend from AbstractAnnotationConfigDispatcherServletInitializer is a way to make spring to load the security config, but I don't use it. A more convinient way to accomplish this can be like this(decalare the dependency of spring security in pom.xml first):
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication().withUser("user").password("user").roles("USER")
.and().withUser("admin").password("admin").roles("USER","ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/").hasRole("USER")
.antMatchers("/index").hasRole("USER")
.antMatchers("/message/*").hasRole("USER")
.anyRequest().permitAll()
.and().formLogin().loginPage("/login").defaultSuccessUrl("/index").failureUrl("/login?error").permitAll()
.and().rememberMe().tokenValiditySeconds(60*60*7).key("message")
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/login").permitAll();
// define your action here.
}
}
Spring will load this config automatically on startup for you, this is enough for spring security to work. As you see, you should define the rules in configure(HttpSecurity http) to tell spring security what to do when a request is coming.
You can just register your security config in your AppInit class by changing the line
context.register(AppConfiguration.class);
to
context.register({HelloWebSecurityConfiguration.class, AppConfiguration.class});

Spring #CacheEvict not working

I've experience in Spring MVC, but first time using Cache. These are steps that I've done yet.
Step : 1
// In spring config
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("user");
}
// Cached Object
public class CachedUser {
private String username;
private String token;
// Public getter-setter
}
// AuthServiceImp
#Service
public class AuthServiceImp implements AuthService {
#Override
#Cacheable(value="user", key="#token")
#Transactional
public CachedUser loadUserDetailsFromDb(String username, String token) {
// codes here
}
#Override
#CacheEvict(value="user", key="#token")
#Transactional
public void removeUser(String username, String token) {
// codes here
}
}
// My Filter
public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {
AuthService authService = WebApplicationContextUtils
.getRequiredWebApplicationContext(this.getServletContext())
.getBean(AuthService.class);
CachedUser user = this.authService.loadUserDetailsFromDb(username, authToken);
}
// Controller
#RestController
public class AuthenticationController {
#Autowired
private AuthService authService;
#GetMapping("logout2")
public ResponseModel logout(#RequestAttribute("username") String username,
HttpServletRequest request) {
String token = request.getHeader(tokenHeader);
authService.removeUser(username, token);
return new ResponseModel(200,"Success",null);
}
}
Whenever calling loadUserDetailsFromDb from AuthenticationTokenFilter it returns cached object (except in first call obviously). That means #Cacheable(value="user", key="#token") is working fine.
But even after I logged out and called authService.removeUser(), calling loadUserDetailsFromDb() fetches the cached object. That means #CacheEvict(value="user", key="#token") is not working.
Step: 2
Referred this and moved removeUser() to another service ( say CacheServiceImp implements CacheService ), yet same problem.
Step: 3
Reffered this and , by my understanding, moved #Cache* annotation to interface AuthService, got following error.
java.lang.IllegalArgumentException: Null key returned for cache
operation (maybe you are using named params on classes without debug
info?)
Note : Is the problem of not evicting, because I'm calling #Cacheable and #CacheEvict methods from different classes. That is from AuthenticationTokenFilter and AuthenticationController
After playing with my code, head and internet, at last, I got this solved. It's a mistake in my Spring (Security) configuration, which I failed to post with the question.
Mistake 1 :
In SecurityInitializer class
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityInitializer() {
super(WebSecurityConfiguration.class);
}
}
As the project includes Spring MVC configuration, the constructor must not be implemented. So removed the constructor. This class, then, simply registers the springSecurityFilterChain Filter for every URL.
Mistake 2: ( THE REAL CAUSE OF ABOVE PROBLEM )
I've added my AuthenticationTokenFilter in two ways:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// other overrides
#Override
protected Filter[] getServletFilters() {
return new Filter[]{ new AuthenticationTokenFilter() };
}
}
and
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
// Other config
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//Other config
httpSecurity.addFilterBefore(authTokenFilter,
UsernamePasswordAuthenticationFilter.class);
}
}
This made the filter to be called twice, one inside Spring context and the other as usual Servlet filter
So removed configuration inside WebAppInitializer
Additional change
Removed #ComponentScan from WebSecurityConfiguration because it's already in SpringMvcConfig. This requires both configurations to be loaded in same context. Done by following code.
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { SpringMvcConfig.class, WebSecurityConfiguration.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
// Removed filter registering from here (Mistake 2)
}
At last, everything working FINE :)

Categories