Search code examples
javaspringspring-bootspring-security

Getting anonymous user after authentication with custom filter


I have post this question but with different format and got no answer and I think I didn't provide enough info.

What I am trying to do is parse a login request payload that contain a JSON with username and password. So, I had to extend UsernamePasswordAuthenticationFilter and replace the UsernamePasswordAuthenticationFilter in filter chain with my custom filter.

I've done that and registered my custom filter as you can see in the log from DefaultSecurityFilterChain:

2023-01-30T22:33:13.993+03:00  INFO 20436 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@1ec09a68, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@7da9b32c, org.springframework.security.web.context.SecurityContextHolderFilter@2b6ff016, org.springframework.security.web.header.HeaderWriterFilter@118c1faa, org.springframework.security.web.authentication.logout.LogoutFilter@4f5df012, ali.yousef.chatty.config.security.JsonUsernamePasswordAuthenticationFilter@7e5c8c80, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7a491a60, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4a0f4282, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@46df794e, org.springframework.security.web.access.ExceptionTranslationFilter@470866d1, org.springframework.security.web.access.intercept.AuthorizationFilter@1d0fc0bc]

Until now everything works fine.

Here is the code:

SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig
{
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
        JsonUsernamePasswordAuthenticationFilter jsonUsernamePasswordAuthenticationFilter = new JsonUsernamePasswordAuthenticationFilter();
        jsonUsernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager);

        http
            .csrf().disable()
            .formLogin().disable()
            .addFilterAfter(jsonUsernamePasswordAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .authorizeHttpRequests()
                .requestMatchers("/api/**").authenticated()
                .anyRequest().permitAll();

        return http.build();
    }
}

JsonUsernamePasswordAuthenticationFilter.java

public class JsonUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    Logger logger = LoggerFactory.getLogger(JsonUsernamePasswordAuthenticationFilter.class);

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        UsernamePasswordDto usernamePasswordDto;

        try {
            usernamePasswordDto = new ObjectMapper().readValue(request.getInputStream(), UsernamePasswordDto.class);
            logger.info(usernamePasswordDto.toString());
        } catch (IOException ioe) {
            throw new AuthenticationServiceException(ioe.getMessage(), ioe);
        }

        UsernamePasswordAuthenticationToken authToken =
                UsernamePasswordAuthenticationToken.unauthenticated(usernamePasswordDto.getUsername(), usernamePasswordDto.getPassword());

        Authentication result = this.getAuthenticationManager().authenticate(authToken);

        for (GrantedAuthority a : result.getAuthorities()) {
            logger.info(a.getAuthority());
        }

        return result;
    }
}

TestController.java

@RestController
public class TestController
{
    @GetMapping("/")
    public String home() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        if (auth == null)
            return "null";

        return auth.getName();
    }
}

I send a request to /login and I get anonymousUser response. I thought I missed something in the custom filter so I printed the authorities the user has and got ROLE_USER.

Here is the full output

2023-01-30T22:48:30.329+03:00  INFO 20900 --- [           main] ali.yousef.chatty.ChattyApplication      : No active profile set, falling back to 1 default profile: "default"
2023-01-30T22:48:30.851+03:00  INFO 20900 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-01-30T22:48:30.903+03:00  INFO 20900 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 37 ms. Found 1 JPA repository interfaces.
2023-01-30T22:48:31.355+03:00  INFO 20900 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-01-30T22:48:31.361+03:00  INFO 20900 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-01-30T22:48:31.361+03:00  INFO 20900 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.5]
2023-01-30T22:48:31.431+03:00  INFO 20900 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-01-30T22:48:31.432+03:00  INFO 20900 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1064 ms
2023-01-30T22:48:31.559+03:00  INFO 20900 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-01-30T22:48:31.591+03:00  INFO 20900 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.1.6.Final
2023-01-30T22:48:31.696+03:00  WARN 20900 --- [           main] org.hibernate.orm.deprecation            : HHH90000021: Encountered deprecated setting [javax.persistence.sharedCache.mode], use [jakarta.persistence.sharedCache.mode] instead
2023-01-30T22:48:31.781+03:00  INFO 20900 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-01-30T22:48:31.983+03:00  INFO 20900 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@77ea806f
2023-01-30T22:48:31.984+03:00  INFO 20900 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-01-30T22:48:32.032+03:00  INFO 20900 --- [           main] SQL dialect                              : HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
2023-01-30T22:48:32.847+03:00  INFO 20900 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-01-30T22:48:32.853+03:00  INFO 20900 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-01-30T22:48:33.070+03:00  WARN 20900 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-01-30T22:48:33.220+03:00  INFO 20900 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page: class path resource [static/index.html]
2023-01-30T22:48:33.519+03:00  INFO 20900 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@5cd6a827, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@6cd65042, org.springframework.security.web.context.SecurityContextHolderFilter@5c30decf, org.springframework.security.web.header.HeaderWriterFilter@5aefdb9e, org.springframework.security.web.authentication.logout.LogoutFilter@50d6af87, ali.yousef.chatty.config.security.JsonUsernamePasswordAuthenticationFilter@7674f9d4, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@4a0f4282, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@356ab368, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@4b5ceb5d, org.springframework.security.web.access.ExceptionTranslationFilter@99f75e4, org.springframework.security.web.access.intercept.AuthorizationFilter@7da9b32c]
2023-01-30T22:48:33.688+03:00  INFO 20900 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-01-30T22:48:33.689+03:00  INFO 20900 --- [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : Starting...
2023-01-30T22:48:33.690+03:00  INFO 20900 --- [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : BrokerAvailabilityEvent[available=true, SimpleBrokerMessageHandler [org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry@5d3a238]]
2023-01-30T22:48:33.690+03:00  INFO 20900 --- [           main] o.s.m.s.b.SimpleBrokerMessageHandler     : Started.
2023-01-30T22:48:33.696+03:00  INFO 20900 --- [           main] ali.yousef.chatty.ChattyApplication      : Started ChattyApplication in 3.673 seconds (process running for 3.952)
2023-01-30T22:48:52.209+03:00  INFO 20900 --- [nio-8080-exec-3] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2023-01-30T22:48:52.209+03:00  INFO 20900 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2023-01-30T22:48:52.209+03:00  INFO 20900 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
2023-01-30T22:48:52.250+03:00  INFO 20900 --- [nio-8080-exec-3] JsonUsernamePasswordAuthenticationFilter : UsernamePasswordDto(username=user01, password=password)
2023-01-30T22:48:52.421+03:00  INFO 20900 --- [nio-8080-exec-3] JsonUsernamePasswordAuthenticationFilter : ROLE_USER

What I think is after finishing the authentication process and sending a redirect to homepage the context is deleted somehow or cleared, but I'm not Spring Security expert.

I've done this in older versions with WebSecurityConfigurerAdapter without any problem but using this way of configuration didn't work.

I really tried everything I could and I would appreciate any help.


Solution

  • I got the solution for my problem from Marcus when I posted an issue on the spring security repository on github because I thought it was a bug. If you want to check the answer yourself you can go here

    The issue was that the my custom filter was using RequestAttributeSecurityContextRepository but the default was DelegatingSecurityContextRepository, hence made the authentication not getting saved between requests.

    Setting the SecurityContextRepository in jsonUsernamePasswordAuthenticationFilter to DelegatingSecurityContextRepository will solve the issue.

    jsonUsernamePasswordAuthenticationFilter.setSecurityContextRepository(new DelegatingSecurityContextRepository(
                    new RequestAttributeSecurityContextRepository(),
                    new HttpSessionSecurityContextRepository()
            ));