Kaynağa Gözat

SEC-2835: Add DelegatingAuthenticationFailureHandler

Add the DelegatingAuthenticationFailureHandler class to support
map each exception to AuthenticationFailureHandler. This class gives
more powerful options to customize default behavior for users.
Kazuki Shimizu 10 yıl önce
ebeveyn
işleme
31234ecef9

+ 85 - 0
web/src/main/java/org/springframework/security/web/authentication/DelegatingAuthenticationFailureHandler.java

@@ -0,0 +1,85 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.web.authentication;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.util.Assert;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * An {@link AuthenticationFailureHandler} that delegates to other
+ * {@link AuthenticationFailureHandler} instances based upon the type of
+ * {@link AuthenticationException} passed into
+ * {@link #onAuthenticationFailure(HttpServletRequest, HttpServletResponse, AuthenticationException)}.
+ *
+ * @author Kazuki Shimizu
+ * @since 4.0
+ */
+public class DelegatingAuthenticationFailureHandler implements AuthenticationFailureHandler {
+
+    private final LinkedHashMap<Class<? extends AuthenticationException>, AuthenticationFailureHandler> handlers;
+
+    private final AuthenticationFailureHandler defaultHandler;
+
+    /**
+     * Creates a new instance
+     *
+     * @param handlers
+     *            a map of the {@link AuthenticationException} class to the
+     *            {@link AuthenticationFailureHandler} that should be used.
+     *            Each is considered in the order they are specified and only
+     *            the first {@link AuthenticationFailureHandler} is ued.
+     *            This parameter cannot specify null or empty.
+     * @param defaultHandler
+     *            the default {@link AuthenticationFailureHandler}
+     *            that should be used if none of the handlers matches.
+     *            This parameter cannot specify null.
+     * @throws IllegalArgumentException if invalid argument is specified
+     */
+    public DelegatingAuthenticationFailureHandler(
+            LinkedHashMap<Class<? extends AuthenticationException>, AuthenticationFailureHandler> handlers,
+            AuthenticationFailureHandler defaultHandler) {
+        Assert.notEmpty(handlers, "handlers cannot be null or empty");
+        Assert.notNull(defaultHandler, "defaultHandler cannot be null");
+        this.handlers = handlers;
+        this.defaultHandler = defaultHandler;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAuthenticationFailure(HttpServletRequest request,
+            HttpServletResponse response, AuthenticationException exception)
+            throws IOException, ServletException {
+        for (Map.Entry<Class<? extends AuthenticationException>, AuthenticationFailureHandler> entry : handlers.entrySet()) {
+            Class<? extends AuthenticationException> handlerMappedExceptionClass = entry.getKey();
+            if (handlerMappedExceptionClass.isAssignableFrom(exception.getClass())) {
+                AuthenticationFailureHandler handler = entry.getValue();
+                handler.onAuthenticationFailure(request, response, exception);
+                return;
+            }
+        }
+        defaultHandler.onAuthenticationFailure(request, response, exception);
+    }
+
+}

+ 143 - 0
web/src/test/java/org/springframework/security/web/authentication/DelegatingAuthenticationFailureHandlerTests.java

@@ -0,0 +1,143 @@
+/*
+ * Copyright 2002-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.web.authentication;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.security.authentication.AccountExpiredException;
+import org.springframework.security.authentication.AccountStatusException;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.CredentialsExpiredException;
+import org.springframework.security.core.AuthenticationException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.LinkedHashMap;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+/**
+ * Test class for {@link org.springframework.security.web.authentication.DelegatingAuthenticationFailureHandler}
+ *
+ * @author Kazuki shimizu
+ * @since 4.0
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class DelegatingAuthenticationFailureHandlerTests {
+
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    @Mock
+    private AuthenticationFailureHandler handler1;
+
+    @Mock
+    private AuthenticationFailureHandler handler2;
+
+    @Mock
+    private AuthenticationFailureHandler defaultHandler;
+
+    @Mock
+    private HttpServletRequest request;
+
+    @Mock
+    private HttpServletResponse response;
+
+    private LinkedHashMap<Class<? extends AuthenticationException>, AuthenticationFailureHandler> handlers;
+
+    private DelegatingAuthenticationFailureHandler handler;
+
+    @Before
+    public void setup() {
+        handlers = new LinkedHashMap<Class<? extends AuthenticationException>, AuthenticationFailureHandler>();
+    }
+
+    @Test
+    public void handleByDefaultHandler() throws Exception {
+        handlers.put(BadCredentialsException.class, handler1);
+        handler = new DelegatingAuthenticationFailureHandler(handlers, defaultHandler);
+
+        AuthenticationException exception = new AccountExpiredException("");
+        handler.onAuthenticationFailure(request, response, exception);
+
+        verifyZeroInteractions(handler1, handler2);
+        verify(defaultHandler).onAuthenticationFailure(request, response, exception);
+    }
+
+    @Test
+    public void handleByMappedHandlerWithSameType() throws Exception {
+        handlers.put(BadCredentialsException.class, handler1); // same type
+        handlers.put(AccountStatusException.class, handler2);
+        handler = new DelegatingAuthenticationFailureHandler(handlers, defaultHandler);
+
+        AuthenticationException exception = new BadCredentialsException("");
+        handler.onAuthenticationFailure(request, response, exception);
+
+        verifyZeroInteractions(handler2, defaultHandler);
+        verify(handler1).onAuthenticationFailure(request, response, exception);
+    }
+
+    @Test
+    public void handleByMappedHandlerWithSuperType() throws Exception {
+        handlers.put(BadCredentialsException.class, handler1);
+        handlers.put(AccountStatusException.class, handler2); // super type of CredentialsExpiredException
+        handler = new DelegatingAuthenticationFailureHandler(handlers, defaultHandler);
+
+        AuthenticationException exception = new CredentialsExpiredException("");
+        handler.onAuthenticationFailure(request, response, exception);
+
+        verifyZeroInteractions(handler1, defaultHandler);
+        verify(handler2).onAuthenticationFailure(request, response, exception);
+    }
+
+    @Test
+    public void handlersIsNull() {
+
+        thrown.expect(IllegalArgumentException.class);
+        thrown.expectMessage("handlers cannot be null or empty");
+
+        new DelegatingAuthenticationFailureHandler(null, defaultHandler);
+
+    }
+
+    @Test
+    public void handlersIsEmpty() {
+
+        thrown.expect(IllegalArgumentException.class);
+        thrown.expectMessage("handlers cannot be null or empty");
+
+        new DelegatingAuthenticationFailureHandler(handlers, defaultHandler);
+
+    }
+
+    @Test
+    public void defaultHandlerIsNull() {
+
+        thrown.expect(IllegalArgumentException.class);
+        thrown.expectMessage("defaultHandler cannot be null");
+
+        handlers.put(BadCredentialsException.class, handler1);
+        new DelegatingAuthenticationFailureHandler(handlers, null);
+
+    }
+
+}