This commit is contained in:
parent
ce9320755d
commit
3580dea399
3 changed files with 90 additions and 39 deletions
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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.keycloak.adapters.springsecurity.authentication;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To return the forbidden code with the corresponding message.
|
||||||
|
*
|
||||||
|
* @author emilienbondu
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class KeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate using the Authorization header");
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,13 @@
|
||||||
|
|
||||||
package org.keycloak.adapters.springsecurity.filter;
|
package org.keycloak.adapters.springsecurity.filter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||||
import org.keycloak.adapters.AdapterTokenStore;
|
import org.keycloak.adapters.AdapterTokenStore;
|
||||||
import org.keycloak.adapters.KeycloakDeployment;
|
import org.keycloak.adapters.KeycloakDeployment;
|
||||||
|
@ -25,7 +32,7 @@ import org.keycloak.adapters.spi.AuthChallenge;
|
||||||
import org.keycloak.adapters.spi.AuthOutcome;
|
import org.keycloak.adapters.spi.AuthOutcome;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
||||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
|
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
||||||
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
|
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
|
||||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
||||||
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
|
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
|
||||||
|
@ -41,19 +48,12 @@ import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a Keycloak authentication processing filter.
|
* Provides a Keycloak authentication processing filter.
|
||||||
*
|
*
|
||||||
|
@ -61,17 +61,15 @@ import java.io.IOException;
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
|
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
|
||||||
public static final String DEFAULT_LOGIN_URL = "/sso/login";
|
|
||||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||||
public static final String SCHEME_BEARER = "bearer ";
|
public static final String SCHEME_BEARER = "bearer ";
|
||||||
public static final String SCHEME_BASIC = "basic ";
|
public static final String SCHEME_BASIC = "basic ";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
|
* Request matcher that matches all requests.
|
||||||
* and any request with a <code>Authorization</code> header.
|
|
||||||
*/
|
*/
|
||||||
public static final RequestMatcher DEFAULT_REQUEST_MATCHER =
|
private static RequestMatcher DEFAULT_REQUEST_MATCHER = new AntPathRequestMatcher("/**");
|
||||||
new OrRequestMatcher(new AntPathRequestMatcher(DEFAULT_LOGIN_URL), new RequestHeaderRequestMatcher(AUTHORIZATION_HEADER));
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
|
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
|
||||||
|
|
||||||
|
@ -89,7 +87,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||||
*/
|
*/
|
||||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
|
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
|
||||||
this(authenticationManager, DEFAULT_REQUEST_MATCHER);
|
this(authenticationManager, DEFAULT_REQUEST_MATCHER);
|
||||||
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(DEFAULT_LOGIN_URL));
|
setAuthenticationFailureHandler(new KeycloakAuthenticationFailureHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,7 +138,18 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||||
log.debug("Auth outcome: {}", result);
|
log.debug("Auth outcome: {}", result);
|
||||||
|
|
||||||
if (AuthOutcome.FAILED.equals(result)) {
|
if (AuthOutcome.FAILED.equals(result)) {
|
||||||
throw new KeycloakAuthenticationException("Auth outcome: " + result);
|
AuthChallenge challenge = authenticator.getChallenge();
|
||||||
|
if (challenge != null) {
|
||||||
|
challenge.challenge(facade);
|
||||||
|
}
|
||||||
|
throw new KeycloakAuthenticationException("Invalid authorization header, see WWW-Authenticate header for details");
|
||||||
|
}
|
||||||
|
if (AuthOutcome.NOT_ATTEMPTED.equals(result)) {
|
||||||
|
AuthChallenge challenge = authenticator.getChallenge();
|
||||||
|
if (challenge != null) {
|
||||||
|
challenge.challenge(facade);
|
||||||
|
}
|
||||||
|
throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
|
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
|
||||||
|
@ -213,18 +222,6 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||||
@Override
|
@Override
|
||||||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
||||||
AuthenticationException failed) throws IOException, ServletException {
|
AuthenticationException failed) throws IOException, ServletException {
|
||||||
|
|
||||||
if (this.isBearerTokenRequest(request)) {
|
|
||||||
SecurityContextHolder.clearContext();
|
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate bearer token");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (this.isBasicAuthRequest(request)) {
|
|
||||||
SecurityContextHolder.clearContext();
|
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.unsuccessfulAuthentication(request, response, failed);
|
super.unsuccessfulAuthentication(request, response, failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.adapters.OidcKeycloakAccount;
|
||||||
import org.keycloak.adapters.spi.HttpFacade;
|
import org.keycloak.adapters.spi.HttpFacade;
|
||||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
||||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
||||||
|
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
||||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||||
|
@ -90,6 +91,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||||
@Mock
|
@Mock
|
||||||
private AuthenticationFailureHandler failureHandler;
|
private AuthenticationFailureHandler failureHandler;
|
||||||
|
|
||||||
|
private KeycloakAuthenticationFailureHandler keycloakFailureHandler;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private OidcKeycloakAccount keycloakAccount;
|
private OidcKeycloakAccount keycloakAccount;
|
||||||
|
|
||||||
|
@ -106,6 +109,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
request = spy(new MockHttpServletRequest());
|
request = spy(new MockHttpServletRequest());
|
||||||
filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
|
filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
|
||||||
|
keycloakFailureHandler = new KeycloakAuthenticationFailureHandler();
|
||||||
|
|
||||||
filter.setApplicationContext(applicationContext);
|
filter.setApplicationContext(applicationContext);
|
||||||
filter.setAuthenticationSuccessHandler(successHandler);
|
filter.setAuthenticationSuccessHandler(successHandler);
|
||||||
|
@ -155,11 +159,13 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||||
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
|
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
|
||||||
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
|
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
|
||||||
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
|
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
|
||||||
|
try {
|
||||||
filter.attemptAuthentication(request, response);
|
filter.attemptAuthentication(request, response);
|
||||||
|
} catch (KeycloakAuthenticationException e) {
|
||||||
verify(response).setStatus(302);
|
verify(response).setStatus(302);
|
||||||
verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
|
verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = KeycloakAuthenticationException.class)
|
@Test(expected = KeycloakAuthenticationException.class)
|
||||||
public void testAttemptAuthenticationWithInvalidToken() throws Exception {
|
public void testAttemptAuthenticationWithInvalidToken() throws Exception {
|
||||||
|
@ -210,8 +216,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||||
this.setBearerAuthHeader(request);
|
this.setBearerAuthHeader(request);
|
||||||
filter.unsuccessfulAuthentication(request, response, exception);
|
filter.unsuccessfulAuthentication(request, response, exception);
|
||||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
|
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||||
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
|
||||||
any(AuthenticationException.class));
|
any(AuthenticationException.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,11 +225,19 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||||
this.setBasicAuthHeader(request);
|
this.setBasicAuthHeader(request);
|
||||||
filter.unsuccessfulAuthentication(request, response, exception);
|
filter.unsuccessfulAuthentication(request, response, exception);
|
||||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
|
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||||
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
|
||||||
any(AuthenticationException.class));
|
any(AuthenticationException.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultFailureHanlder() throws Exception {
|
||||||
|
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||||
|
filter.setAuthenticationFailureHandler(keycloakFailureHandler);
|
||||||
|
filter.unsuccessfulAuthentication(request, response, exception);
|
||||||
|
|
||||||
|
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), any(String.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test(expected = UnsupportedOperationException.class)
|
@Test(expected = UnsupportedOperationException.class)
|
||||||
public void testSetAllowSessionCreation() throws Exception {
|
public void testSetAllowSessionCreation() throws Exception {
|
||||||
filter.setAllowSessionCreation(true);
|
filter.setAllowSessionCreation(true);
|
||||||
|
|
Loading…
Reference in a new issue