Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Security custom AuthenticationSuccessHandler is ignored

I'm setting up a pure json rest service with Spring boot + data rest and now having trouble getting my custom authentication success handler (as well as authentication failure handler) to handle login responses. Login itself works as it should but the server responds to a successful login with a status 302 without redirect url (this triggers an error e.g. in javascript's XMLHttpRequest) and completely ignores my handlers set in config.

WebSecurityConfig:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private RESTLogoutSuccessHandler logoutSuccessHandler;

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
    return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {

    http
        .cors();

    http
        .exceptionHandling()
        .authenticationEntryPoint(authenticationEntryPoint);

    http
        .formLogin()
        .permitAll()
        .loginProcessingUrl("/login")
        .successHandler(authenticationSuccessHandler)
        .failureHandler(authenticationFailureHandler);

    http
        .logout()
        .permitAll()
        .logoutUrl("/logout")
        .logoutSuccessHandler(logoutSuccessHandler);

    http
        .sessionManagement()
        .maximumSessions(1);

    http.addFilterAt(getAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);

    http.authorizeRequests().anyRequest().authenticated();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.addAllowedOrigin("*");
    configuration.setAllowCredentials(true);
    configuration.setExposedHeaders(Arrays.asList("X-CSRF-TOKEN"));
       configuration.setAllowedHeaders(Arrays.asList("X-CSRF-TOKEN", "content-type"));
    configuration.setAllowedMethods(Arrays.asList("GET","POST","OPTIONS"));
    configuration.setMaxAge(3600L);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

@Bean
public PermissionEvaluator customPermissionEvaluator() {
    return new CustomPermissionEvaluator();
}

protected CustomUsernamePasswordAuthenticationFilter getAuthenticationFilter() {
    CustomUsernamePasswordAuthenticationFilter authFilter = new CustomUsernamePasswordAuthenticationFilter();
    try { 
        authFilter.setAuthenticationManager(this.authenticationManagerBean());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return authFilter;
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
}

AuthenticationSuccessHandler:

@Component
public class RESTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                    Authentication authentication) throws IOException, ServletException {
    response.setStatus(HttpServletResponse.SC_OK);
    PrintWriter writer = response.getWriter();
    writer.write("Login OK");
    writer.flush();        
    clearAuthenticationAttributes(request);
}
}

CustomUsernamePasswordAuthenticationFilter just reads username and password from json, it does not override the filter() method:

public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

private final Logger log = LoggerFactory.getLogger(this.getClass());

private boolean postOnly = true;

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

    if (postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException(
                "Authentication method not supported: " + request.getMethod());
    }

    LoginRequest loginRequest;
    try {
        BufferedReader reader = request.getReader();
        StringBuffer sb = new StringBuffer();
        String line = null;
        while ((line = reader.readLine()) != null){
            sb.append(line);
        }
        ObjectMapper mapper = new ObjectMapper();
        loginRequest = mapper.readValue(sb.toString(), LoginRequest.class);
    } catch (Exception ex) {
        throw new AuthenticationServiceException("Unable to read login credentials.");
    }

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
            loginRequest.getEmail(), loginRequest.getPassword());

    setDetails(request, authRequest);
    return this.getAuthenticationManager().authenticate(authRequest);
}
}

In debug log I can see that there is a strange RequestAwareAuthenticationSuccessHandler that gets the request after login handler and just passes a default redirect to '/':

2016-12-28 15:44:02.358 DEBUG 6194 --- [nio-8080-exec-7] o.s.s.authentication.ProviderManager     : Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2016-12-28 15:44:02.358 DEBUG 6194 --- [nio-8080-exec-7] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [xxx.server.service.impl.UserDetailsServiceImpl.loadUserByUsername]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly; ''
2016-12-28 15:44:02.358 DEBUG 6194 --- [nio-8080-exec-7] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@908810e] for JPA transaction
2016-12-28 15:44:02.358 DEBUG 6194 --- [nio-8080-exec-7] o.s.jdbc.datasource.DataSourceUtils      : Setting JDBC Connection [ProxyConnection[PooledConnection[org.postgresql.jdbc.PgConnection@4d362a0f]]] read-only
2016-12-28 15:44:02.358 DEBUG 6194 --- [nio-8080-exec-7] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC transaction [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@7cf6e5f]
[...]
2016-12-28 15:44:02.380 DEBUG 6194 --- [nio-8080-exec-7] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2016-12-28 15:44:02.380 DEBUG 6194 --- [nio-8080-exec-7] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@908810e]
2016-12-28 15:44:02.381 DEBUG 6194 --- [nio-8080-exec-7] o.s.jdbc.datasource.DataSourceUtils      : Resetting read-only flag of JDBC Connection [ProxyConnection[PooledConnection[org.postgresql.jdbc.PgConnection@4d362a0f]]]
2016-12-28 15:44:02.381 DEBUG 6194 --- [nio-8080-exec-7] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@908810e] after transaction
2016-12-28 15:44:02.381 DEBUG 6194 --- [nio-8080-exec-7] o.s.orm.jpa.EntityManagerFactoryUtils    : Closing JPA EntityManager
2016-12-28 15:44:02.488 DEBUG 6194 --- [nio-8080-exec-7] RequestAwareAuthenticationSuccessHandler : Using default Url: /
2016-12-28 15:44:02.488 DEBUG 6194 --- [nio-8080-exec-7] o.s.s.web.DefaultRedirectStrategy        : Redirecting to '/'
2016-12-28 15:44:02.488 DEBUG 6194 --- [nio-8080-exec-7] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@273d8edd
2016-12-28 15:44:02.488 DEBUG 6194 --- [nio-8080-exec-7] w.c.HttpSessionSecurityContextRepository : SecurityContext 'org.springframework.security.core.context.SecurityContextImpl@faa222b9: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@faa222b9: Principal: org.springframework.security.core.userdetails.User@fa84ebb2: Username: [email protected]; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@fffed504: RemoteIpAddress: 127.0.0.1; SessionId: 1F3FF4A3203600AE56E3CB391BE96EFC; Granted Authorities: USER' stored to HttpSession: 'org.apache.catalina.session.StandardSessionFacade@692480d1
2016-12-28 15:44:02.488 DEBUG 6194 --- [nio-8080-exec-7] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
like image 319
TeemuP Avatar asked Dec 28 '16 13:12

TeemuP


1 Answers

http
        .formLogin()
        .permitAll()
        .loginProcessingUrl("/login")
        .successHandler(authenticationSuccessHandler)
        .failureHandler(authenticationFailureHandler);

Your own authenticationSuccessHandler injects to UsernamePasswordAuthenticationFilter,and add the CustomUsernamePasswordAuthenticationFilter that extend UsernamePasswordAuthenticationFilter instead of UsernamePasswordAuthenticationFilter

http.addFilterAt(getAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

But your own CustomUsernamePasswordAuthenticationFilter uses the default success handler.

protected CustomUsernamePasswordAuthenticationFilter getAuthenticationFilter() {
    CustomUsernamePasswordAuthenticationFilter authFilter = new CustomUsernamePasswordAuthenticationFilter();
    try { 
        authFilter.setAuthenticationManager(this.authenticationManagerBean());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return authFilter;
}

I can't see any code that you inject your success handler to CustomUsernamePasswordAuthenticationFilter.

you need add the success handler to CustomUsernamePasswordAuthenticationFilter.

authFilter.setAuthenticationManager(this.authenticationManagerBean());
// set handler           
authFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);       
authFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
like image 75
chaoluo Avatar answered Oct 02 '22 04:10

chaoluo