Skip to content

Commit

Permalink
Introduce support for AuthenticationFailureHandler (#682)
Browse files Browse the repository at this point in the history
To be used in conjunction with AbstractAuthenticationProcessingFilter
for rendering problem+json
  • Loading branch information
chicobento authored Oct 1, 2021
1 parent 336be14 commit 0f62aa0
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import static org.apiguardian.api.API.Status.INTERNAL;
import static org.apiguardian.api.API.Status.STABLE;

/**
* A compound {@link AuthenticationEntryPoint} and {@link AccessDeniedHandler} that delegates exceptions to
* Spring WebMVC's {@link HandlerExceptionResolver} as defined in {@link WebMvcConfigurationSupport}.
* A compound {@link AuthenticationEntryPoint}, {@link AuthenticationFailureHandler} and {@link AccessDeniedHandler}
* that delegates exceptions to Spring WebMVC's {@link HandlerExceptionResolver} as defined in {@link WebMvcConfigurationSupport}.
*
* Compatible with spring-webmvc 4.3.3.
*/
@API(status = STABLE)
@Component
public class SecurityProblemSupport implements AuthenticationEntryPoint, AccessDeniedHandler {
public class SecurityProblemSupport implements AuthenticationEntryPoint, AuthenticationFailureHandler, AccessDeniedHandler {

private final HandlerExceptionResolver resolver;

Expand All @@ -42,6 +45,12 @@ public void commence(final HttpServletRequest request, final HttpServletResponse
resolver.resolveException(request, response, null, exception);
}

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
resolver.resolveException(request, response, null, exception);
}

@Override
public void handle(final HttpServletRequest request, final HttpServletResponse response,
final AccessDeniedException exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
Expand All @@ -31,6 +37,10 @@
import org.zalando.problem.spring.common.MediaTypes;
import org.zalando.problem.spring.web.advice.ProblemHandling;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Locale;

Expand Down Expand Up @@ -104,9 +114,25 @@ public void configure(final HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport);
http.addFilterBefore(new AuthenticationFilter(problemSupport), LogoutFilter.class);
}

}

public static class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {

protected AuthenticationFilter(SecurityProblemSupport problemSupport) {
super(new AntPathRequestMatcher("/authFilter"));
setAuthenticationFailureHandler(problemSupport);
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
throw new BadCredentialsException("invalid pass");
}

}

@ControllerAdvice
public static class ExceptionHandling implements ProblemHandling, SecurityAdviceTrait {
Expand Down Expand Up @@ -137,7 +163,7 @@ void notAuthenticated() throws Exception {
}

@Test
void notAuthorized() throws Exception {
void notAuthorizedByRole() throws Exception {
mvc.perform(get("/greet").param("name", "Alice").with(user("user").roles("USER")))
.andExpect(status().isForbidden())
.andExpect(content().contentType(MediaTypes.PROBLEM))
Expand All @@ -146,4 +172,13 @@ void notAuthorized() throws Exception {
.andExpect(jsonPath("$.detail", is("Access is denied")));
}

@Test
void notAuthorizedByFilter() throws Exception {
mvc.perform(get("/authFilter").param("name", "Alice").with(user("user").roles("ADMIN")))
.andExpect(status().isUnauthorized())
.andExpect(content().contentType(MediaTypes.PROBLEM))
.andExpect(jsonPath("$.title", is("Unauthorized")))
.andExpect(jsonPath("$.status", is(401)))
.andExpect(jsonPath("$.detail", is("invalid pass")));
}
}

0 comments on commit 0f62aa0

Please sign in to comment.