Browse Source
Support integration of the Spring SecurityContext on Callable's used with WebAsyncManager by registering SecurityContextCallableProcessingInterceptor.pull/17/head
29 changed files with 583 additions and 35 deletions
@ -0,0 +1,49 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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.core; |
||||||
|
|
||||||
|
import static org.fest.assertions.Assertions.assertThat; |
||||||
|
|
||||||
|
import java.io.DataInputStream; |
||||||
|
import java.io.InputStream; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Rob Winch |
||||||
|
* |
||||||
|
*/ |
||||||
|
public class JavaVersionTests { |
||||||
|
|
||||||
|
private static final int JDK5_CLASS_VERSION = 49; |
||||||
|
|
||||||
|
@Test |
||||||
|
public void authenticationCorrectJdkCompatibility() throws Exception { |
||||||
|
assertClassVersion(Authentication.class); |
||||||
|
} |
||||||
|
|
||||||
|
private void assertClassVersion(Class<?> clazz) throws Exception { |
||||||
|
String classResourceName = clazz.getName().replaceAll("\\.", "/") + ".class"; |
||||||
|
InputStream input = Thread.currentThread().getContextClassLoader().getResourceAsStream(classResourceName); |
||||||
|
try { |
||||||
|
DataInputStream data = new DataInputStream(input); |
||||||
|
data.readInt(); |
||||||
|
data.readShort(); // minor
|
||||||
|
int major = data.readShort(); |
||||||
|
assertThat(major).isEqualTo(JDK5_CLASS_VERSION); |
||||||
|
} finally { |
||||||
|
try { input.close(); } catch(Exception e) {} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,79 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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.context.request.async; |
||||||
|
|
||||||
|
import java.util.concurrent.Callable; |
||||||
|
|
||||||
|
import org.springframework.security.core.context.SecurityContext; |
||||||
|
import org.springframework.security.core.context.SecurityContextHolder; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.web.context.request.NativeWebRequest; |
||||||
|
import org.springframework.web.context.request.async.CallableProcessingInterceptor; |
||||||
|
import org.springframework.web.context.request.async.CallableProcessingInterceptorAdapter; |
||||||
|
|
||||||
|
/** |
||||||
|
* <p> |
||||||
|
* Allows for integration with Spring MVC's {@link Callable} support. |
||||||
|
* </p> |
||||||
|
* <p> |
||||||
|
* A {@link CallableProcessingInterceptor} that establishes the injected {@link SecurityContext} on the |
||||||
|
* {@link SecurityContextHolder} when {@link #preProcess(NativeWebRequest, Callable)} is invoked. It also clear out the |
||||||
|
* {@link SecurityContextHolder} by invoking {@link SecurityContextHolder#clearContext()} in the |
||||||
|
* {@link #afterCompletion(NativeWebRequest, Callable)} method. |
||||||
|
* </p> |
||||||
|
* |
||||||
|
* @author Rob Winch |
||||||
|
* @since 3.2 |
||||||
|
*/ |
||||||
|
public final class SecurityContextCallableProcessingInterceptor extends CallableProcessingInterceptorAdapter { |
||||||
|
private SecurityContext securityContext; |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new {@link SecurityContextCallableProcessingInterceptor} that uses the {@link SecurityContext} from the |
||||||
|
* {@link SecurityContextHolder} at the time {@link #beforeConcurrentHandling(NativeWebRequest, Callable)} is invoked. |
||||||
|
*/ |
||||||
|
public SecurityContextCallableProcessingInterceptor() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link SecurityContextCallableProcessingInterceptor} with the specified {@link SecurityContext}. |
||||||
|
* @param securityContext the {@link SecurityContext} to set on the {@link SecurityContextHolder} in |
||||||
|
* {@link #preProcess(NativeWebRequest, Callable)}. Cannot be null. |
||||||
|
* @throws IllegalArgumentException if {@link SecurityContext} is null. |
||||||
|
*/ |
||||||
|
public SecurityContextCallableProcessingInterceptor(SecurityContext securityContext) { |
||||||
|
Assert.notNull(securityContext, "securityContext cannot be null"); |
||||||
|
setSecurityContext(securityContext); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception { |
||||||
|
if(securityContext == null) { |
||||||
|
setSecurityContext(SecurityContextHolder.getContext()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception { |
||||||
|
SecurityContextHolder.clearContext(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception { |
||||||
|
SecurityContextHolder.setContext(securityContext); |
||||||
|
} |
||||||
|
|
||||||
|
private void setSecurityContext(SecurityContext securityContext) { |
||||||
|
this.securityContext = securityContext; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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.context.request.async; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.concurrent.Callable; |
||||||
|
|
||||||
|
import javax.servlet.FilterChain; |
||||||
|
import javax.servlet.ServletException; |
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
|
||||||
|
import org.springframework.security.core.context.SecurityContext; |
||||||
|
import org.springframework.web.context.request.async.WebAsyncManager; |
||||||
|
import org.springframework.web.context.request.async.WebAsyncUtils; |
||||||
|
import org.springframework.web.filter.OncePerRequestFilter; |
||||||
|
|
||||||
|
/** |
||||||
|
* Provides integration between the {@link SecurityContext} and Spring Web's {@link WebAsyncManager} by using the |
||||||
|
* {@link SecurityContextCallableProcessingInterceptor#beforeConcurrentHandling(org.springframework.web.context.request.NativeWebRequest, Callable)} |
||||||
|
* to populate the {@link SecurityContext} on the {@link Callable}. |
||||||
|
* |
||||||
|
* @author Rob Winch |
||||||
|
* @see SecurityContextCallableProcessingInterceptor |
||||||
|
*/ |
||||||
|
public final class WebAsyncManagerIntegrationFilter extends OncePerRequestFilter { |
||||||
|
private static final Object CALLABLE_INTERCEPTOR_KEY = new Object(); |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
||||||
|
throws ServletException, IOException { |
||||||
|
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); |
||||||
|
|
||||||
|
SecurityContextCallableProcessingInterceptor securityProcessingInterceptor = (SecurityContextCallableProcessingInterceptor) asyncManager.getCallableInterceptor(CALLABLE_INTERCEPTOR_KEY); |
||||||
|
if (securityProcessingInterceptor == null) { |
||||||
|
asyncManager.registerCallableInterceptor(CALLABLE_INTERCEPTOR_KEY, |
||||||
|
new SecurityContextCallableProcessingInterceptor()); |
||||||
|
} |
||||||
|
|
||||||
|
filterChain.doFilter(request, response); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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.context.request.async; |
||||||
|
|
||||||
|
import static org.fest.assertions.Assertions.assertThat; |
||||||
|
|
||||||
|
import java.util.concurrent.Callable; |
||||||
|
|
||||||
|
import org.junit.After; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.runners.MockitoJUnitRunner; |
||||||
|
import org.springframework.security.core.context.SecurityContext; |
||||||
|
import org.springframework.security.core.context.SecurityContextHolder; |
||||||
|
import org.springframework.web.context.request.NativeWebRequest; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Rob Winch |
||||||
|
* |
||||||
|
*/ |
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class SecurityContextCallableProcessingInterceptorTests { |
||||||
|
@Mock |
||||||
|
private SecurityContext securityContext; |
||||||
|
@Mock |
||||||
|
private Callable<?> callable; |
||||||
|
@Mock |
||||||
|
private NativeWebRequest webRequest; |
||||||
|
|
||||||
|
@After |
||||||
|
public void clearSecurityContext() { |
||||||
|
SecurityContextHolder.clearContext(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class) |
||||||
|
public void constructorNull() { |
||||||
|
new SecurityContextCallableProcessingInterceptor(null); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void currentSecurityContext() throws Exception { |
||||||
|
SecurityContextCallableProcessingInterceptor interceptor = new SecurityContextCallableProcessingInterceptor(); |
||||||
|
SecurityContextHolder.setContext(securityContext); |
||||||
|
interceptor.beforeConcurrentHandling(webRequest, callable); |
||||||
|
SecurityContextHolder.clearContext(); |
||||||
|
|
||||||
|
interceptor.preProcess(webRequest, callable); |
||||||
|
assertThat(SecurityContextHolder.getContext()).isSameAs(securityContext); |
||||||
|
|
||||||
|
interceptor.afterCompletion(webRequest, callable); |
||||||
|
assertThat(SecurityContextHolder.getContext()).isNotSameAs(securityContext); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void specificSecurityContext() throws Exception { |
||||||
|
SecurityContextCallableProcessingInterceptor interceptor = new SecurityContextCallableProcessingInterceptor( |
||||||
|
securityContext); |
||||||
|
|
||||||
|
interceptor.preProcess(webRequest, callable); |
||||||
|
assertThat(SecurityContextHolder.getContext()).isSameAs(securityContext); |
||||||
|
|
||||||
|
interceptor.afterCompletion(webRequest, callable); |
||||||
|
assertThat(SecurityContextHolder.getContext()).isNotSameAs(securityContext); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,127 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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.context.request.async; |
||||||
|
|
||||||
|
import static org.fest.assertions.Assertions.assertThat; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import java.util.concurrent.Callable; |
||||||
|
import java.util.concurrent.ThreadFactory; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
import javax.servlet.http.HttpServletResponse; |
||||||
|
|
||||||
|
import org.junit.After; |
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.runners.MockitoJUnitRunner; |
||||||
|
import org.springframework.core.task.SimpleAsyncTaskExecutor; |
||||||
|
import org.springframework.mock.web.MockFilterChain; |
||||||
|
import org.springframework.security.core.context.SecurityContext; |
||||||
|
import org.springframework.security.core.context.SecurityContextHolder; |
||||||
|
import org.springframework.web.context.request.async.AsyncWebRequest; |
||||||
|
import org.springframework.web.context.request.async.WebAsyncManager; |
||||||
|
import org.springframework.web.context.request.async.WebAsyncUtils; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Rob Winch |
||||||
|
* |
||||||
|
*/ |
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class WebAsyncManagerIntegrationFilterTests { |
||||||
|
@Mock |
||||||
|
private SecurityContext securityContext; |
||||||
|
@Mock |
||||||
|
private HttpServletRequest request; |
||||||
|
@Mock |
||||||
|
private HttpServletResponse response; |
||||||
|
@Mock |
||||||
|
private AsyncWebRequest asyncWebRequest; |
||||||
|
private WebAsyncManager asyncManager; |
||||||
|
private JoinableThreadFactory threadFactory; |
||||||
|
|
||||||
|
private MockFilterChain filterChain; |
||||||
|
|
||||||
|
private WebAsyncManagerIntegrationFilter filter; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
when(asyncWebRequest.getNativeRequest(HttpServletRequest.class)).thenReturn(request); |
||||||
|
when(request.getRequestURI()).thenReturn("/"); |
||||||
|
filterChain = new MockFilterChain(); |
||||||
|
|
||||||
|
threadFactory = new JoinableThreadFactory(); |
||||||
|
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor(); |
||||||
|
executor.setThreadFactory(threadFactory); |
||||||
|
|
||||||
|
asyncManager = WebAsyncUtils.getAsyncManager(request); |
||||||
|
asyncManager.setAsyncWebRequest(asyncWebRequest); |
||||||
|
asyncManager.setTaskExecutor(executor); |
||||||
|
when(request.getAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE)).thenReturn(asyncManager); |
||||||
|
|
||||||
|
filter = new WebAsyncManagerIntegrationFilter(); |
||||||
|
} |
||||||
|
|
||||||
|
@After |
||||||
|
public void clearSecurityContext() { |
||||||
|
SecurityContextHolder.clearContext(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void doFilterInternalRegistersSecurityContextCallableProcessor() throws Exception { |
||||||
|
SecurityContextHolder.setContext(securityContext); |
||||||
|
filter.doFilterInternal(request, response, filterChain); |
||||||
|
|
||||||
|
VerifyingCallable verifyingCallable = new VerifyingCallable(); |
||||||
|
asyncManager.startCallableProcessing(verifyingCallable); |
||||||
|
threadFactory.join(); |
||||||
|
assertThat(asyncManager.getConcurrentResult()).isSameAs(securityContext); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void doFilterInternalRegistersSecurityContextCallableProcessorContextUpdated() throws Exception { |
||||||
|
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); |
||||||
|
filter.doFilterInternal(request, response, filterChain); |
||||||
|
SecurityContextHolder.setContext(securityContext); |
||||||
|
|
||||||
|
VerifyingCallable verifyingCallable = new VerifyingCallable(); |
||||||
|
asyncManager.startCallableProcessing(verifyingCallable); |
||||||
|
threadFactory.join(); |
||||||
|
assertThat(asyncManager.getConcurrentResult()).isSameAs(securityContext); |
||||||
|
} |
||||||
|
|
||||||
|
private static final class JoinableThreadFactory implements ThreadFactory { |
||||||
|
private Thread t; |
||||||
|
|
||||||
|
public Thread newThread(Runnable r) { |
||||||
|
t = new Thread(r); |
||||||
|
return t; |
||||||
|
} |
||||||
|
|
||||||
|
public void join() throws InterruptedException { |
||||||
|
t.join(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private class VerifyingCallable implements Callable<SecurityContext> { |
||||||
|
|
||||||
|
public SecurityContext call() throws Exception { |
||||||
|
return SecurityContextHolder.getContext(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue